‪TYPO3CMS  10.4
AbstractMenuContentObject.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
37 
46 {
52  protected ‪$menuNumber = 1;
53 
59  protected ‪$entryLevel = 0;
60 
67 
71  protected ‪$alwaysActivePIDlist = [];
72 
78  public ‪$parent_cObj;
79 
85  protected ‪$MP_array = [];
86 
92  protected ‪$conf = [];
93 
99  protected ‪$mconf = [];
100 
104  protected ‪$tmpl;
105 
109  protected ‪$sys_page;
110 
116  protected ‪$id;
117 
124  protected ‪$nextActive;
125 
131  protected ‪$menuArr;
132 
136  protected ‪$hash;
137 
141  protected ‪$result = [];
142 
150 
154  protected ‪$I;
155 
159  protected ‪$WMresult;
160 
164  protected ‪$WMmenuItems;
165 
170 
174  protected ‪$WMcObj;
175 
181  protected ‪$alternativeMenuTempArray = '';
182 
188  protected ‪$parentMenuArrItemKey;
189 
193  protected ‪$parentMenuArr;
194 
195  protected const ‪customItemStates = [
196  // IFSUB is TRUE if there exist submenu items to the current item
197  'IFSUB',
198  'ACT',
199  // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
200  'ACTIFSUB',
201  // CUR is TRUE if the current page equals the item here!
202  'CUR',
203  // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
204  'CURIFSUB',
205  'USR',
206  'SPC',
207  'USERDEF1',
208  'USERDEF2'
209  ];
210 
223  public function ‪start(‪$tmpl, ‪$sys_page, ‪$id, ‪$conf, ‪$menuNumber, $objSuffix = '')
224  {
225  $tsfe = $this->‪getTypoScriptFrontendController();
226  $this->conf = ‪$conf;
227  $this->menuNumber = ‪$menuNumber;
228  $this->mconf = ‪$conf[$this->menuNumber . $objSuffix . '.'];
229  $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
230  // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the PageRepository object
231  if ($this->conf[$this->menuNumber . $objSuffix] && is_object(‪$tmpl) && is_object(‪$sys_page)) {
232  $this->tmpl = ‪$tmpl;
233  $this->sys_page = ‪$sys_page;
234  // alwaysActivePIDlist initialized:
235  if (trim($this->conf['alwaysActivePIDlist']) || isset($this->conf['alwaysActivePIDlist.'])) {
236  if (isset($this->conf['alwaysActivePIDlist.'])) {
237  $this->conf['alwaysActivePIDlist'] = $this->parent_cObj->stdWrap(
238  $this->conf['alwaysActivePIDlist'],
239  $this->conf['alwaysActivePIDlist.']
240  );
241  }
242  $this->alwaysActivePIDlist = ‪GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
243  }
244  // includeNotInMenu initialized:
245  $includeNotInMenu = $this->conf['includeNotInMenu'];
246  $includeNotInMenuConf = $this->conf['includeNotInMenu.'] ?? null;
247  $this->conf['includeNotInMenu'] = is_array($includeNotInMenuConf)
248  ? $this->parent_cObj->stdWrap($includeNotInMenu, $includeNotInMenuConf)
249  : $includeNotInMenu;
250  // exclude doktypes that should not be shown in menu (e.g. backend user section)
251  if ($this->conf['excludeDoktypes'] ?? false) {
252  $this->excludedDoktypes = ‪GeneralUtility::intExplode(',', $this->conf['excludeDoktypes']);
253  }
254  // EntryLevel
255  $this->entryLevel = $this->parent_cObj->getKey(
256  isset(‪$conf['entryLevel.']) ? $this->parent_cObj->stdWrap(
257  ‪$conf['entryLevel'],
258  ‪$conf['entryLevel.']
259  ) : ‪$conf['entryLevel'],
260  $this->tmpl->rootLine
261  );
262  // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
263  // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
264  if (‪$id) {
265  $this->id = (int)‪$id;
266  } else {
267  // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
268  $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
269  // Traverse rootline to build MP_array of pages BEFORE the entryLevel
270  // (MP var for ->id is picked up in the next part of the code...)
271  foreach ($this->tmpl->rootLine as ‪$entryLevel => $levelRec) {
272  // For overlaid mount points, set the variable right now:
273  if (($levelRec['_MP_PARAM'] ?? false) && ($levelRec['_MOUNT_OL'] ?? false)) {
274  $this->MP_array[] = $levelRec['_MP_PARAM'];
275  }
276  // Break when entry level is reached:
277  if (‪$entryLevel >= $this->entryLevel) {
278  break;
279  }
280  // For normal mount points, set the variable for next level.
281  if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
282  $this->MP_array[] = $levelRec['_MP_PARAM'];
283  }
284  }
285  }
286  // Return FALSE if no page ID was set (thus no menu of subpages can be made).
287  if ($this->id <= 0) {
288  return false;
289  }
290  // Check if page is a mount point, and if so set id and MP_array
291  // (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...)
292  $mount_info = $this->sys_page->getMountPointInfo($this->id);
293  if (is_array($mount_info)) {
294  $this->MP_array[] = $mount_info['MPvar'];
295  $this->id = $mount_info['mount_pid'];
296  }
297  // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
298  // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
299  if ($this->rL_uidRegister === null) {
300  $this->rL_uidRegister = [];
301  $rl_MParray = [];
302  foreach ($this->tmpl->rootLine as $v_rl) {
303  // For overlaid mount points, set the variable right now:
304  if (($v_rl['_MP_PARAM'] ?? false) && ($v_rl['_MOUNT_OL'] ?? false)) {
305  $rl_MParray[] = $v_rl['_MP_PARAM'];
306  }
307  // Add to register:
308  $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
309  (
310  !empty($rl_MParray)
311  ? ':' . implode(',', $rl_MParray)
312  : ''
313  );
314  // For normal mount points, set the variable for next level.
315  if (($v_rl['_MP_PARAM'] ?? false) && !($v_rl['_MOUNT_OL'] ?? false)) {
316  $rl_MParray[] = $v_rl['_MP_PARAM'];
317  }
318  }
319  }
320  // Set $directoryLevel so the following evaluation of the nextActive will not return
321  // an invalid value if .special=directory was set
322  $directoryLevel = 0;
323  if ($this->conf['special'] === 'directory') {
324  $value = isset($this->conf['special.']['value.']) ? $this->parent_cObj->stdWrap(
325  $this->conf['special.']['value'],
326  $this->conf['special.']['value.']
327  ) : $this->conf['special.']['value'];
328  if ($value === '') {
329  $value = $tsfe->page['uid'];
330  }
331  $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
332  }
333  // 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
334  // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
335  $startLevel = $directoryLevel ?: ‪$this->entryLevel;
336  $currentLevel = $startLevel + ‪$this->menuNumber;
337  if (is_array($this->tmpl->rootLine[$currentLevel] ?? null)) {
338  $nextMParray = ‪$this->MP_array;
339  if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
340  // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
341  // otherwise automatic expansion will not work
342  $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
343  if (isset($parentRecord['_MP_PARAM'])) {
344  $nextMParray[] = $parentRecord['_MP_PARAM'];
345  }
346  }
347  // In overlay mode, add next level MPvars as well:
348  if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
349  $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
350  }
351  $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
352  (
353  !empty($nextMParray)
354  ? ':' . implode(',', $nextMParray)
355  : ''
356  );
357  } else {
358  $this->nextActive = '';
359  }
360  return true;
361  }
362  $this->‪getTimeTracker()->‪setTSlogMessage('ERROR in menu', 3);
363  return false;
364  }
365 
372  public function ‪makeMenu()
373  {
374  if (!$this->id) {
375  return;
376  }
377 
378  // Initializing showAccessRestrictedPages
379  $SAVED_where_groupAccess = '';
380  if ($this->mconf['showAccessRestrictedPages'] ?? false) {
381  // SAVING where_groupAccess
382  $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
383  // Temporarily removing fe_group checking!
384  $this->sys_page->where_groupAccess = '';
385  }
386 
387  $menuItems = $this->‪prepareMenuItems();
388 
389  $c = 0;
390  $c_b = 0;
391 
392  $minItems = (int)(($this->mconf['minItems'] ?? 0) ?: ($this->conf['minItems'] ?? 0));
393  $maxItems = (int)(($this->mconf['maxItems'] ?? 0) ?: ($this->conf['maxItems'] ?? 0));
394  $begin = $this->parent_cObj->calc(($this->mconf['begin'] ?? 0) ?: ($this->conf['begin'] ?? 0));
395  $minItemsConf = $this->mconf['minItems.'] ?? $this->conf['minItems.'] ?? null;
396  $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
397  $maxItemsConf = $this->mconf['maxItems.'] ?? $this->conf['maxItems.'] ?? null;
398  $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
399  $beginConf = $this->mconf['begin.'] ?? $this->conf['begin.'] ?? null;
400  $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
401  $banUidArray = $this->‪getBannedUids();
402  // Fill in the menuArr with elements that should go into the menu:
403  $this->menuArr = [];
404  foreach ($menuItems as $data) {
405  $isSpacerPage = (int)$data['doktype'] === ‪PageRepository::DOKTYPE_SPACER || $data['ITEM_STATE'] === 'SPC';
406  // if item is a spacer, $spacer is set
407  if ($this->‪filterMenuPages($data, $banUidArray, $isSpacerPage)) {
408  $c_b++;
409  // If the beginning item has been reached.
410  if ($begin <= $c_b) {
411  $this->menuArr[$c] = $this->‪determineOriginalShortcutPage($data);
412  $this->menuArr[$c]['isSpacer'] = $isSpacerPage;
413  $c++;
414  if ($maxItems && $c >= $maxItems) {
415  break;
416  }
417  }
418  }
419  }
420  // Fill in fake items, if min-items is set.
421  if ($minItems) {
422  while ($c < $minItems) {
423  $this->menuArr[$c] = [
424  'title' => '...',
425  'uid' => $this->‪getTypoScriptFrontendController()->id
426  ];
427  $c++;
428  }
429  }
430  // Passing the menuArr through a user defined function:
431  if ($this->mconf['itemArrayProcFunc'] ?? false) {
432  $this->menuArr = $this->‪userProcess('itemArrayProcFunc', $this->menuArr);
433  }
434  // Setting number of menu items
435  $this->‪getTypoScriptFrontendController()->register['count_menuItems'] = count($this->menuArr);
436  $this->hash = md5(
437  json_encode($this->menuArr) .
438  json_encode($this->mconf) .
439  json_encode($this->tmpl->rootLine) .
440  json_encode($this->MP_array)
441  );
442  // Get the cache timeout:
443  if ($this->conf['cache_period'] ?? false) {
444  $cacheTimeout = $this->conf['cache_period'];
445  } else {
446  $cacheTimeout = $this->‪getTypoScriptFrontendController()->‪get_cache_timeout();
447  }
448  $cache = $this->‪getCache();
449  $cachedData = $cache->get($this->hash);
450  if (!is_array($cachedData)) {
451  $this->‪generate();
452  $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
453  } else {
454  $this->result = $cachedData;
455  }
456  // End showAccessRestrictedPages
457  if ($this->mconf['showAccessRestrictedPages'] ?? false) {
458  // RESTORING where_groupAccess
459  $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
460  }
461  }
462 
468  public function ‪generate()
469  {
470  }
471 
475  public function ‪writeMenu()
476  {
477  return '';
478  }
479 
486  protected function ‪removeInaccessiblePages(array $pages)
487  {
488  $banned = $this->‪getBannedUids();
489  $filteredPages = [];
490  foreach ($pages as $aPage) {
491  if ($this->‪filterMenuPages($aPage, $banned, (int)$aPage['doktype'] === ‪PageRepository::DOKTYPE_SPACER)) {
492  $filteredPages[$aPage['uid']] = $aPage;
493  }
494  }
495  return $filteredPages;
496  }
497 
503  protected function ‪prepareMenuItems()
504  {
505  $menuItems = [];
506  $alternativeSortingField = trim($this->mconf['alternativeSortingField'] ?? '') ?: 'sorting';
507 
508  // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
509  $additionalWhere = $this->mconf['additionalWhere'] ?? '';
510  if (isset($this->mconf['additionalWhere.'])) {
511  $additionalWhere = $this->parent_cObj->stdWrap($additionalWhere, $this->mconf['additionalWhere.']);
512  }
513  $additionalWhere .= $this->‪getDoktypeExcludeWhere();
514 
515  // ... only for the FIRST level of a HMENU
516  if ($this->menuNumber == 1 && $this->conf['special']) {
517  $value = isset($this->conf['special.']['value.'])
518  ? $this->parent_cObj->stdWrap($this->conf['special.']['value'], $this->conf['special.']['value.'])
519  : $this->conf['special.']['value'];
520  switch ($this->conf['special']) {
521  case 'userfunction':
522  $menuItems = $this->‪prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
523  break;
524  case 'language':
525  $menuItems = $this->‪prepareMenuItemsForLanguageMenu($value);
526  break;
527  case 'directory':
528  $menuItems = $this->‪prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
529  break;
530  case 'list':
531  $menuItems = $this->prepareMenuItemsForListMenu($value);
532  break;
533  case 'updated':
534  $menuItems = $this->‪prepareMenuItemsForUpdatedMenu(
535  $value,
536  $this->mconf['alternativeSortingField'] ?: false
537  );
538  break;
539  case 'keywords':
540  $menuItems = $this->‪prepareMenuItemsForKeywordsMenu(
541  $value,
542  $this->mconf['alternativeSortingField'] ?: false
543  );
544  break;
545  case 'categories':
547  $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
548  $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
549  break;
550  case 'rootline':
551  $menuItems = $this->‪prepareMenuItemsForRootlineMenu();
552  break;
553  case 'browse':
554  $menuItems = $this->‪prepareMenuItemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
555  break;
556  }
557  if ($this->mconf['sectionIndex'] ?? false) {
558  $sectionIndexes = [];
559  foreach ($menuItems as $page) {
560  $sectionIndexes = $sectionIndexes + $this->‪sectionIndex($alternativeSortingField, $page['uid']);
561  }
562  $menuItems = $sectionIndexes;
563  }
564  } elseif (is_array($this->alternativeMenuTempArray)) {
565  // Setting $menuItems array if not level 1.
567  } elseif ($this->mconf['sectionIndex']) {
568  $menuItems = $this->‪sectionIndex($alternativeSortingField);
569  } else {
570  // Default: Gets a hierarchical menu based on subpages of $this->id
571  $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
572  }
573  return $menuItems;
574  }
575 
583  protected function ‪prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
584  {
585  $menuItems = $this->parent_cObj->callUserFunction(
586  $this->conf['special.']['userFunc'],
587  array_merge($this->conf['special.'], ['value' => $specialValue, '_altSortField' => $sortingField]),
588  ''
589  );
590  return is_array($menuItems) ? $menuItems : [];
591  }
592 
599  protected function ‪prepareMenuItemsForLanguageMenu($specialValue)
600  {
601  $menuItems = [];
602  // Getting current page record NOT overlaid by any translation:
603  $tsfe = $this->‪getTypoScriptFrontendController();
604  $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->page['uid']);
605 
606  if ($specialValue === 'auto') {
607  $site = $this->‪getCurrentSite();
608  $languages = $site->getLanguages();
609  ‪$languageItems = array_keys($languages);
610  } else {
611  ‪$languageItems = ‪GeneralUtility::intExplode(',', $specialValue);
612  }
613 
614  $tsfe->register['languages_HMENU'] = implode(',', ‪$languageItems);
615 
616  $currentLanguageId = $this->‪getCurrentLanguageAspect()->getId();
617 
618  foreach (‪$languageItems as $sUid) {
619  // Find overlay record:
620  if ($sUid) {
621  $lRecs = $this->sys_page->getPageOverlay($tsfe->page['uid'], $sUid);
622  } else {
623  $lRecs = [];
624  }
625  // Checking if the "disabled" state should be set.
626  if (GeneralUtility::hideIfNotTranslated($tsfe->page['l18n_cfg']) && $sUid &&
627  empty($lRecs) || GeneralUtility::hideIfDefaultLanguage($tsfe->page['l18n_cfg']) &&
628  (!$sUid || empty($lRecs)) ||
629  !($this->conf['special.']['normalWhenNoLanguage'] ?? false) && $sUid && empty($lRecs)
630  ) {
631  $iState = $currentLanguageId === $sUid ? 'USERDEF2' : 'USERDEF1';
632  } else {
633  $iState = $currentLanguageId === $sUid ? 'ACT' : 'NO';
634  }
635  $getVars = '';
636  if ($this->conf['addQueryString']) {
637  $getVars = $this->parent_cObj->getQueryArguments(
638  $this->conf['addQueryString.'],
639  [],
640  true
641  );
642  }
643  // Adding menu item:
644  $menuItems[] = array_merge(
645  array_merge($currentPageWithNoOverlay, $lRecs),
646  [
647  '_PAGES_OVERLAY_REQUESTEDLANGUAGE' => $sUid,
648  'ITEM_STATE' => $iState,
649  '_ADD_GETVARS' => $getVars,
650  '_SAFE' => true
651  ]
652  );
653  }
654  return $menuItems;
655  }
656 
664  protected function ‪prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
665  {
666  $tsfe = $this->‪getTypoScriptFrontendController();
667  $menuItems = [];
668  if ($specialValue == '') {
669  $specialValue = $tsfe->page['uid'];
670  }
671  $items = ‪GeneralUtility::intExplode(',', $specialValue);
672  $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
673  foreach ($items as ‪$id) {
674  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps(‪$id);
675  // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
676  $mount_info = $this->sys_page->getMountPointInfo(‪$id);
677  if (is_array($mount_info)) {
678  if ($mount_info['overlay']) {
679  // Overlays should already have their full MPvars calculated:
680  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid']);
681  $MP = $MP ?: $mount_info['MPvar'];
682  } else {
683  $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
684  }
685  ‪$id = $mount_info['mount_pid'];
686  }
687  // Get sub-pages:
688  $statement = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => ‪$id, 'orderBy' => $sortingField]);
689  while ($row = $statement->fetch()) {
690  // When the site language configuration is in "free" mode, then the page without overlay is fetched
691  // (which is kind-of strange for pages, but this is what exec_getQuery() is doing)
692  // this means, that $row is a translated page, but hasn't been overlaid. For this reason, we fetch
693  // the default translation page again, (which does a ->getPageOverlay() again - doing this on a
694  // translated page would result in no record at all)
695  if ($row['l10n_parent'] > 0 && !isset($row['_PAGES_OVERLAY'])) {
696  $row = $this->sys_page->getPage($row['l10n_parent'], true);
697  }
698  $tsfe->sys_page->versionOL('pages', $row, true);
699  if (!empty($row)) {
700  // Keep mount point?
701  $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
702  // There is a valid mount point.
703  if (is_array($mount_info) && $mount_info['overlay']) {
704  // Using "getPage" is OK since we need the check for enableFields
705  // AND for type 2 of mount pids we DO require a doktype < 200!
706  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
707  if (!empty($mp_row)) {
708  $row = $mp_row;
709  $row['_MP_PARAM'] = $mount_info['MPvar'];
710  } else {
711  // If the mount point could not be fetched with respect
712  // to enableFields, unset the row so it does not become a part of the menu!
713  unset($row);
714  }
715  }
716  // Add external MP params, then the row:
717  if (!empty($row)) {
718  if ($MP) {
719  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
720  }
721  $menuItems[] = $this->sys_page->getPageOverlay($row);
722  }
723  }
724  }
725  }
726 
727  return $menuItems;
728  }
729 
736  protected function prepareMenuItemsForListMenu($specialValue)
737  {
738  $menuItems = [];
739  if ($specialValue == '') {
740  $specialValue = ‪$this->id;
741  }
742  $skippedEnableFields = [];
743  if (!empty($this->mconf['showAccessRestrictedPages'])) {
744  $skippedEnableFields = ['fe_group' => 1];
745  }
747  $loadDB = GeneralUtility::makeInstance(RelationHandler::class);
748  $loadDB->setFetchAllFields(true);
749  $loadDB->start($specialValue, 'pages');
750  $loadDB->additionalWhere['pages'] = $this->sys_page->enableFields('pages', -1, $skippedEnableFields);
751  $loadDB->getFromDB();
752  $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
753  foreach ($loadDB->itemArray as $val) {
754  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$val['id']);
755  // Keep mount point?
756  $mount_info = $this->sys_page->getMountPointInfo($val['id']);
757  // There is a valid mount point.
758  if (is_array($mount_info) && $mount_info['overlay']) {
759  // Using "getPage" is OK since we need the check for enableFields
760  // AND for type 2 of mount pids we DO require a doktype < 200!
761  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
762  if (!empty($mp_row)) {
763  $row = $mp_row;
764  $row['_MP_PARAM'] = $mount_info['MPvar'];
765  // Overlays should already have their full MPvars calculated
766  if ($mount_info['overlay']) {
767  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid']);
768  if ($MP) {
769  unset($row['_MP_PARAM']);
770  }
771  }
772  } else {
773  // If the mount point could not be fetched with respect to
774  // enableFields, unset the row so it does not become a part of the menu!
775  unset($row);
776  }
777  } else {
778  $row = $loadDB->results['pages'][$val['id']];
779  }
780  // Add versioning overlay for current page (to respect workspaces)
781  if (isset($row) && is_array($row)) {
782  $this->sys_page->versionOL('pages', $row, true);
783  }
784  // Add external MP params, then the row:
785  if (isset($row) && is_array($row)) {
786  if ($MP) {
787  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
788  }
789  $menuItems[] = $this->sys_page->getPageOverlay($row);
790  }
791  }
792  return $menuItems;
793  }
794 
802  protected function ‪prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
803  {
804  $tsfe = $this->‪getTypoScriptFrontendController();
805  $menuItems = [];
806  if ($specialValue == '') {
807  $specialValue = $tsfe->page['uid'];
808  }
809  $items = ‪GeneralUtility::intExplode(',', $specialValue);
810  if (‪MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
811  $depth = ‪MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
812  } else {
813  $depth = 20;
814  }
815  // Max number of items
816  $limit = ‪MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
817  $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
818  if (!$limit) {
819  $limit = 10;
820  }
821  // 'auto', 'manual', 'tstamp'
822  $mode = $this->conf['special.']['mode'];
823  // Get id's
824  $beginAtLevel = ‪MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
825  $id_list_arr = [];
826  foreach ($items as ‪$id) {
827  // Exclude the current ID if beginAtLevel is > 0
828  if ($beginAtLevel > 0) {
829  $id_list_arr[] = $this->parent_cObj->getTreeList(‪$id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
830  } else {
831  $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * ‪$id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
832  }
833  }
834  $id_list = implode(',', $id_list_arr);
835  // Get sortField (mode)
836  switch ($mode) {
837  case 'starttime':
838  $sortField = 'starttime';
839  break;
840  case 'lastUpdated':
841  case 'manual':
842  $sortField = 'lastUpdated';
843  break;
844  case 'tstamp':
845  $sortField = 'tstamp';
846  break;
847  case 'crdate':
848  $sortField = 'crdate';
849  break;
850  default:
851  $sortField = 'SYS_LASTCHANGED';
852  }
853  $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->‪getDoktypeExcludeWhere();
854  if ($this->conf['special.']['excludeNoSearchPages']) {
855  $extraWhere .= ' AND pages.no_search=0';
856  }
857  if ($maxAge > 0) {
858  $extraWhere .= ' AND ' . $sortField . '>' . (‪$GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
859  }
860  $statement = $this->parent_cObj->exec_getQuery('pages', [
861  'pidInList' => '0',
862  'uidInList' => $id_list,
863  'where' => $sortField . '>=0' . $extraWhere,
864  'orderBy' => $sortingField ?: $sortField . ' DESC',
865  'max' => $limit
866  ]);
867  while ($row = $statement->fetch()) {
868  // When the site language configuration is in "free" mode, then the page without overlay is fetched
869  // (which is kind-of strange for pages, but this is what exec_getQuery() is doing)
870  // this means, that $row is a translated page, but hasn't been overlaid. For this reason, we fetch
871  // the default translation page again, (which does a ->getPageOverlay() again - doing this on a
872  // translated page would result in no record at all)
873  if ($row['l10n_parent'] > 0 && !isset($row['_PAGES_OVERLAY'])) {
874  $row = $this->sys_page->getPage($row['l10n_parent'], true);
875  }
876  $tsfe->sys_page->versionOL('pages', $row, true);
877  if (is_array($row)) {
878  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
879  }
880  }
881 
882  return $menuItems;
883  }
884 
892  protected function ‪prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
893  {
894  $tsfe = $this->‪getTypoScriptFrontendController();
895  $menuItems = [];
896  [$specialValue] = ‪GeneralUtility::intExplode(',', $specialValue);
897  if (!$specialValue) {
898  $specialValue = $tsfe->page['uid'];
899  }
900  if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
901  $kw = isset($this->conf['special.']['setKeywords.']) ? $this->parent_cObj->stdWrap($this->conf['special.']['setKeywords'], $this->conf['special.']['setKeywords.']) : $this->conf['special.']['setKeywords'];
902  } else {
903  // The page record of the 'value'.
904  $value_rec = $this->sys_page->getPage($specialValue);
905  $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ?: 'keywords';
906  // keywords.
907  $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
908  }
909  // *'auto', 'manual', 'tstamp'
910  $mode = $this->conf['special.']['mode'];
911  switch ($mode) {
912  case 'starttime':
913  $sortField = 'starttime';
914  break;
915  case 'lastUpdated':
916  case 'manual':
917  $sortField = 'lastUpdated';
918  break;
919  case 'tstamp':
920  $sortField = 'tstamp';
921  break;
922  case 'crdate':
923  $sortField = 'crdate';
924  break;
925  default:
926  $sortField = 'SYS_LASTCHANGED';
927  }
928  // Depth, limit, extra where
929  if (‪MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
930  $depth = ‪MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
931  } else {
932  $depth = 20;
933  }
934  // Max number of items
935  $limit = ‪MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
936  // Start point
937  $eLevel = $this->parent_cObj->getKey(
938  isset($this->conf['special.']['entryLevel.'])
939  ? $this->parent_cObj->stdWrap($this->conf['special.']['entryLevel'], $this->conf['special.']['entryLevel.'])
940  : $this->conf['special.']['entryLevel'],
941  $this->tmpl->rootLine
942  );
943  $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
944  // Which field is for keywords
945  $kfield = 'keywords';
946  ‪if ($this->conf['special.']['keywordsField']) {
947  [$kfield] = explode(' ', trim($this->conf['special.']['keywordsField']));
948  }
949  // If there are keywords and the startuid is present
950  if ($kw && $startUid) {
951  $bA = ‪MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
952  $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
953  $kwArr = ‪GeneralUtility::trimExplode(',', $kw, true);
954  $keyWordsWhereArr = [];
955  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
956  foreach ($kwArr as $word) {
957  $keyWordsWhereArr[] = $queryBuilder->expr()->like(
958  $kfield,
959  $queryBuilder->createNamedParameter(
960  '%' . $queryBuilder->escapeLikeWildcards($word) . '%',
961  \PDO::PARAM_STR
962  )
963  );
964  }
965  $queryBuilder
966  ->select('*')
967  ->from('pages')
968  ->where(
969  $queryBuilder->expr()->in(
970  'uid',
971  ‪GeneralUtility::intExplode(',', $id_list, true)
972  ),
973  $queryBuilder->expr()->neq(
974  'uid',
975  $queryBuilder->createNamedParameter($specialValue, \PDO::PARAM_INT)
976  )
977  );
978 
979  if (!empty($keyWordsWhereArr)) {
980  $queryBuilder->andWhere($queryBuilder->expr()->orX(...$keyWordsWhereArr));
981  }
982 
983  if (!empty($this->excludedDoktypes)) {
984  $queryBuilder->andWhere(
985  $queryBuilder->expr()->notIn(
986  'pages.doktype',
987  $this->excludedDoktypes
988  )
989  );
990  }
991 
992  if (!$this->conf['includeNotInMenu']) {
993  $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.nav_hide', 0));
994  }
995 
996  if ($this->conf['special.']['excludeNoSearchPages']) {
997  $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.no_search', 0));
998  }
999 
1000  if ($limit > 0) {
1001  $queryBuilder->setMaxResults($limit);
1002  }
1004  if ($sortingField) {
1005  $queryBuilder->orderBy($sortingField);
1006  } else {
1007  $queryBuilder->orderBy($sortField, 'desc');
1008  }
1009 
1010  ‪$result = $queryBuilder->execute();
1011  while ($row = ‪$result->fetch()) {
1012  $tsfe->sys_page->versionOL('pages', $row, true);
1013  if (is_array($row)) {
1014  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
1015  }
1016  }
1017  }
1018 
1019  return $menuItems;
1020  }
1021 
1027  protected function ‪prepareMenuItemsForRootlineMenu()
1028  {
1029  $menuItems = [];
1030  $range = isset($this->conf['special.']['range.'])
1031  ? $this->parent_cObj->stdWrap($this->conf['special.']['range'], $this->conf['special.']['range.'])
1032  : $this->conf['special.']['range'];
1033  $begin_end = explode('|', $range);
1034  $begin_end[0] = (int)$begin_end[0];
1035  if (!‪MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1036  $begin_end[1] = -1;
1037  }
1038  $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1039  $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1040  if ($endKey < $beginKey) {
1041  $endKey = $beginKey;
1042  }
1043  $rl_MParray = [];
1044  foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1045  // For overlaid mount points, set the variable right now:
1046  if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1047  $rl_MParray[] = $v_rl['_MP_PARAM'];
1048  }
1049  // Traverse rootline:
1050  if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1051  $temp_key = $k_rl;
1052  $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1053  if (!empty($menuItems[$temp_key])) {
1054  // If there are no specific target for the page, put the level specific target on.
1055  if (!$menuItems[$temp_key]['target']) {
1056  $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1057  $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1058  }
1059  } else {
1060  unset($menuItems[$temp_key]);
1061  }
1062  }
1063  // For normal mount points, set the variable for next level.
1064  if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1065  $rl_MParray[] = $v_rl['_MP_PARAM'];
1066  }
1067  }
1068  // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1069  if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1070  $menuItems = array_reverse($menuItems);
1071  }
1072  return $menuItems;
1073  }
1074 
1083  protected function ‪prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1084  {
1085  $menuItems = [];
1086  [$specialValue] = ‪GeneralUtility::intExplode(',', $specialValue);
1087  if (!$specialValue) {
1088  $specialValue = $this->‪getTypoScriptFrontendController()->page['uid'];
1089  }
1090  // Will not work out of rootline
1091  if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1092  $recArr = [];
1093  // The page record of the 'value'.
1094  $value_rec = $this->sys_page->getPage($specialValue);
1095  // 'up' page cannot be outside rootline
1096  if ($value_rec['pid']) {
1097  // The page record of 'up'.
1098  $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1099  }
1100  // If the 'up' item was NOT level 0 in rootline...
1101  if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1102  // The page record of "index".
1103  $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1104  }
1105  // check if certain pages should be excluded
1106  $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->‪getDoktypeExcludeWhere();
1107  if ($this->conf['special.']['excludeNoSearchPages']) {
1108  $additionalWhere .= ' AND pages.no_search=0';
1109  }
1110  // prev / next is found
1111  $prevnext_menu = $this->‪removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1112  $lastKey = 0;
1113  ‪$nextActive = 0;
1114  foreach ($prevnext_menu as $k_b => $v_b) {
1115  if (‪$nextActive) {
1116  $recArr['next'] = $v_b;
1117  ‪$nextActive = 0;
1118  }
1119  if ($v_b['uid'] == $specialValue) {
1120  if ($lastKey) {
1121  $recArr['prev'] = $prevnext_menu[$lastKey];
1122  }
1123  ‪$nextActive = 1;
1124  }
1125  $lastKey = $k_b;
1126  }
1127 
1128  $recArr['first'] = reset($prevnext_menu);
1129  $recArr['last'] = end($prevnext_menu);
1130  // prevsection / nextsection is found
1131  // You can only do this, if there is a valid page two levels up!
1132  if (!empty($recArr['index']['uid'])) {
1133  $prevnextsection_menu = $this->‪removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1134  $lastKey = 0;
1135  ‪$nextActive = 0;
1136  foreach ($prevnextsection_menu as $k_b => $v_b) {
1137  if (‪$nextActive) {
1138  $sectionRec_temp = $this->‪removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1139  if (!empty($sectionRec_temp)) {
1140  $recArr['nextsection'] = reset($sectionRec_temp);
1141  $recArr['nextsection_last'] = end($sectionRec_temp);
1142  ‪$nextActive = 0;
1143  }
1144  }
1145  if ($v_b['uid'] == $value_rec['pid']) {
1146  if ($lastKey) {
1147  $sectionRec_temp = $this->‪removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1148  if (!empty($sectionRec_temp)) {
1149  $recArr['prevsection'] = reset($sectionRec_temp);
1150  $recArr['prevsection_last'] = end($sectionRec_temp);
1151  }
1152  }
1153  ‪$nextActive = 1;
1154  }
1155  $lastKey = $k_b;
1156  }
1157  }
1158  if ($this->conf['special.']['items.']['prevnextToSection']) {
1159  if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1160  $recArr['prev'] = $recArr['prevsection_last'];
1161  }
1162  if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1163  $recArr['next'] = $recArr['nextsection'];
1164  }
1165  }
1166  $items = explode('|', $this->conf['special.']['items']);
1167  $c = 0;
1168  foreach ($items as $k_b => $v_b) {
1169  $v_b = strtolower(trim($v_b));
1170  if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1171  $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1172  }
1173  if (is_array($recArr[$v_b])) {
1174  $menuItems[$c] = $recArr[$v_b];
1175  if ($this->conf['special.'][$v_b . '.']['target']) {
1176  $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1177  }
1178  $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1179  if (is_array($tmpSpecialFields)) {
1180  foreach ($tmpSpecialFields as $fk => $val) {
1181  $menuItems[$c][$fk] = $val;
1182  }
1183  }
1184  $c++;
1185  }
1186  }
1187  }
1188  return $menuItems;
1189  }
1190 
1202  public function ‪filterMenuPages(&$data, $banUidArray, $isSpacerPage)
1203  {
1204  $includePage = true;
1205  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] ?? [] as $className) {
1206  $hookObject = GeneralUtility::makeInstance($className);
1207  if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1208  throw new \UnexpectedValueException($className . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1209  }
1210  $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $isSpacerPage, $this);
1211  }
1212  if (!$includePage) {
1213  return false;
1214  }
1215  if ($data['_SAFE']) {
1216  return true;
1217  }
1218  // If the spacer-function is not enabled, spacers will not enter the $menuArr
1219  if (!$this->mconf['SPC'] && $isSpacerPage) {
1220  return false;
1221  }
1222  // Page may not be a 'Backend User Section' or any other excluded doktype
1223  if (in_array((int)$data['doktype'], $this->excludedDoktypes, true)) {
1224  return false;
1225  }
1226  // PageID should not be banned
1227  if (in_array((int)$data['uid'], $banUidArray, true)) {
1228  return false;
1229  }
1230  // If the page is hide in menu, but the menu does not include them do not show the page
1231  if ($data['nav_hide'] && !$this->conf['includeNotInMenu']) {
1232  return false;
1233  }
1234  // Checking if a page should be shown in the menu depending on whether a translation exists or if the default language is disabled
1235  if (!$this->sys_page->isPageSuitableForLanguage($data, $this->getCurrentLanguageAspect())) {
1236  return false;
1237  }
1238  // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1239  if ($this->‪getCurrentLanguageAspect()->getId() > 0 && $this->conf['protectLvar']) {
1240  if ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1241  $olRec = $this->sys_page->getPageOverlay($data['uid'], $this->‪getCurrentLanguageAspect()->getId());
1242  if (empty($olRec)) {
1243  // If no page translation record then page can NOT be accessed in
1244  // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1245  $data['_ADD_GETVARS'] .= '&L=0';
1246  }
1247  }
1248  }
1249  return true;
1250  }
1251 
1265  protected function ‪processItemStates($splitCount)
1266  {
1267  // Prepare normal settings
1268  if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1269  // Setting a blank array if NO=1 and there are no properties.
1270  $this->mconf['NO.'] = [];
1271  }
1272  $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1273  $NOconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['NO.'], $splitCount);
1274 
1275  // Prepare custom states settings, overriding normal settings
1276  foreach (self::customItemStates as $state) {
1277  if (empty($this->mconf[$state])) {
1278  continue;
1279  }
1280  $customConfiguration = null;
1281  foreach ($NOconf as $key => $val) {
1282  if ($this->‪isItemState($state, $key)) {
1283  // if this is the first element of type $state, we must generate the custom configuration.
1284  if ($customConfiguration === null) {
1285  $customConfiguration = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf[$state . '.'], $splitCount);
1286  }
1287  // Substitute normal with the custom (e.g. IFSUB)
1288  if (isset($customConfiguration[$key])) {
1289  $NOconf[$key] = $customConfiguration[$key];
1290  }
1291  }
1292  }
1293  }
1294 
1295  return $NOconf;
1296  }
1297 
1307  protected function ‪link($key, $altTarget, $typeOverride)
1308  {
1309  $runtimeCache = $this->‪getRuntimeCache();
1310  $MP_var = $this->‪getMPvar($key);
1311  $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . $MP_var . json_encode($this->menuArr[$key]));
1312  $runtimeCachedLink = $runtimeCache->get($cacheId);
1313  if ($runtimeCachedLink !== false) {
1314  return $runtimeCachedLink;
1315  }
1316 
1317  $tsfe = $this->‪getTypoScriptFrontendController();
1318 
1319  // If a user script returned the value overrideId in the menu array we use that as page id
1320  if (($this->mconf['overrideId'] ?? false) || ($this->menuArr[$key]['overrideId'] ?? false)) {
1321  $overrideId = (int)($this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId']);
1322  $overrideId = $overrideId > 0 ? $overrideId : null;
1323  // Clear MP parameters since ID was changed.
1324  $MP_params = '';
1325  } else {
1326  $overrideId = null;
1327  // Mount points:
1328  $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1329  }
1330  // Setting main target:
1331  if ($altTarget) {
1332  $mainTarget = $altTarget;
1333  } elseif ($this->mconf['target.']) {
1334  $mainTarget = $this->parent_cObj->stdWrap($this->mconf['target'], $this->mconf['target.']);
1335  } else {
1336  $mainTarget = $this->mconf['target'];
1337  }
1338  // Creating link:
1339  $addParams = ($this->mconf['addParams'] ?? '') . $MP_params;
1340  if (($this->mconf['collapse'] ?? false) && $this->‪isActive($this->menuArr[$key]['uid'], $this->‪getMPvar($key))) {
1341  $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1342  $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1343  $LD = $this->‪menuTypoLink($thePage, $mainTarget, $addParams, $typeOverride, $overrideId);
1344  } else {
1345  $addParams .= ($this->I['val']['additionalParams'] ?? '') . $this->menuArr[$key]['_ADD_GETVARS'];
1346  $LD = $this->‪menuTypoLink($this->menuArr[$key], $mainTarget, $addParams, $typeOverride, $overrideId);
1347  }
1348  // Override default target configuration if option is set
1349  if ($this->menuArr[$key]['target']) {
1350  $LD['target'] = $this->menuArr[$key]['target'];
1351  }
1352  // Override URL if using "External URL"
1353  if ((int)$this->menuArr[$key]['doktype'] === ‪PageRepository::DOKTYPE_LINK) {
1354  $externalUrl = (string)$this->sys_page->getExtURL($this->menuArr[$key]);
1355  // Create link using typolink (concerning spamProtectEmailAddresses) for email links
1356  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $externalUrl]);
1357  // Links to emails should not have any target
1358  if (stripos($externalUrl, 'mailto:') === 0) {
1359  $LD['target'] = '';
1360  // use external target for the URL
1361  } elseif (empty($LD['target']) && !empty($tsfe->extTarget)) {
1362  $LD['target'] = $tsfe->extTarget;
1363  }
1364  }
1365 
1366  // Override url if current page is a shortcut
1367  $shortcut = null;
1368  if ((int)$this->menuArr[$key]['doktype'] === ‪PageRepository::DOKTYPE_SHORTCUT && (int)$this->menuArr[$key]['shortcut_mode'] !== ‪PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1369  $menuItem = $this->menuArr[$key];
1370  try {
1371  $shortcut = $tsfe->sys_page->getPageShortcut(
1372  $menuItem['shortcut'],
1373  $menuItem['shortcut_mode'],
1374  $menuItem['uid'],
1375  20,
1376  [],
1377  true
1378  );
1379  if (isset($menuItem['_PAGES_OVERLAY_LANGUAGE'])) {
1380  $shortcut = $tsfe->sys_page->getPageOverlay($shortcut, $menuItem['_PAGES_OVERLAY_LANGUAGE']);
1381  }
1382  } catch (\Exception $ex) {
1383  }
1384  if (!is_array($shortcut)) {
1385  $runtimeCache->set($cacheId, []);
1386  return [];
1387  }
1388  // Only setting url, not target
1389  $LD['totalURL'] = $this->parent_cObj->typoLink_URL([
1390  'parameter' => $shortcut['uid'],
1391  'language' => $shortcut['_PAGES_OVERLAY_REQUESTEDLANGUAGE'] ?? 'current',
1392  'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1393  'linkAccessRestrictedPages' => !empty($this->mconf['showAccessRestrictedPages'])
1394  ]);
1395  }
1396  if ($shortcut) {
1397  $pageData = $shortcut;
1398  $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1399  } else {
1400  $pageData = $this->menuArr[$key];
1401  }
1402  // Manipulation in case of access restricted pages:
1403  $this->‪changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1404  // Overriding URL / Target if set to do so:
1405  if ($this->menuArr[$key]['_OVERRIDE_HREF'] ?? false) {
1406  $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1407  if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1408  $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1409  }
1410  }
1411  // OnClick open in windows.
1412  $onClick = '';
1413  if ($this->mconf['JSWindow'] ?? false) {
1414  ‪$conf = $this->mconf['JSWindow.'];
1415  $url = $LD['totalURL'];
1416  $LD['totalURL'] = '#';
1417  $onClick = 'openPic('
1418  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1419  . '\'' . (‪$conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1420  . GeneralUtility::quoteJSvalue(‪$conf['params']) . '); return false;';
1421  $tsfe->setJS('openPic');
1422  }
1423  // look for type and popup
1424  // following settings are valid in field target:
1425  // 230 will add type=230 to the link
1426  // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1427  // 230 _blank will add type=230 to the link and open with target "_blank"
1428  // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1429  $matches = [];
1430  $targetIsType = $LD['target'] && ‪MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1431  if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) || $targetIsType) {
1432  // has type?
1433  if ((int)($matches[1] ?? 0) || $targetIsType) {
1434  $LD['totalURL'] .= (strpos($LD['totalURL'], '?') === false ? '?' : '&') . 'type=' . ($targetIsType ?: (int)$matches[1]);
1435  $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1436  }
1437  // Open in popup window?
1438  if (($matches[3] ?? false) && ($matches[4] ?? false)) {
1439  $target = $LD['target'] ?? 'FEopenLink';
1440  $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1441  $onClick = 'openPic('
1442  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1443  . ',' . GeneralUtility::quoteJSvalue($target) . ',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1444  $tsfe->setJS('openPic');
1445  $LD['target'] = '';
1446  }
1447  }
1448  // out:
1449  $list = [];
1450  // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1451  // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1452  // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1453  $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1454  $list['TARGET'] = $LD['target'];
1455  $list['onClick'] = $onClick;
1456  $runtimeCache->set($cacheId, $list);
1457  return $list;
1458  }
1459 
1469  protected function ‪determineOriginalShortcutPage(array $page)
1470  {
1471  // Check if modification is required
1472  if (
1473  $this->‪getCurrentLanguageAspect()->getId() > 0
1474  && empty($page['shortcut'])
1475  && !empty($page['uid'])
1476  && !empty($page['_PAGES_OVERLAY'])
1477  && !empty($page['_PAGES_OVERLAY_UID'])
1478  ) {
1479  // Using raw record since the record was overlaid and is correct already:
1480  $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1481 
1482  if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1483  $page['shortcut'] = $originalPage['shortcut'];
1484  }
1485  }
1486 
1487  return $page;
1488  }
1489 
1498  protected function ‪changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1499  {
1500  // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1501  if (($this->mconf['showAccessRestrictedPages'] ?? false) && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->‪getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1502  $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1503  $addParams = str_replace(
1504  [
1505  '###RETURN_URL###',
1506  '###PAGE_ID###'
1507  ],
1508  [
1509  rawurlencode($LD['totalURL']),
1510  $page['_SHORTCUT_PAGE_UID'] ?? $page['uid']
1511  ],
1512  $this->mconf['showAccessRestrictedPages.']['addParams']
1513  );
1514  $LD = $this->‪menuTypoLink($thePage, $mainTarget, $addParams, $typeOverride);
1515  }
1516  }
1517 
1525  protected function ‪subMenu($uid, $objSuffix)
1526  {
1527  // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1528  $altArray = '';
1529  if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU'] ?? null) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1530  $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1531  }
1532  // Make submenu if the page is the next active
1533  $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix] ?? '';
1534  // stdWrap for expAll
1535  if (isset($this->mconf['expAll.'])) {
1536  $this->mconf['expAll'] = $this->parent_cObj->stdWrap($this->mconf['expAll'], $this->mconf['expAll.']);
1537  }
1538  if ((($this->mconf['expAll'] ?? false) || $this->‪isNext($uid, $this->‪getMPvar($this->I['key'])) || is_array($altArray)) && !($this->mconf['sectionIndex'] ?? false)) {
1539  try {
1540  $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1542  $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1543  $submenu->entryLevel = $this->entryLevel + 1;
1544  $submenu->rL_uidRegister = ‪$this->rL_uidRegister;
1545  $submenu->MP_array = ‪$this->MP_array;
1546  if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1547  $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1548  }
1549  // Especially scripts that build the submenu needs the parent data
1550  $submenu->parent_cObj = ‪$this->parent_cObj;
1551  $submenu->setParentMenu($this->menuArr, $this->I['key']);
1552  // Setting alternativeMenuTempArray (will be effective only if an array)
1553  if (is_array($altArray)) {
1554  $submenu->alternativeMenuTempArray = $altArray;
1555  }
1556  if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1557  $submenu->makeMenu();
1558  // Memorize the current menu item count
1560  $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1561  // Reset the menu item count for the submenu
1562  $tsfe->register['count_MENUOBJ'] = 0;
1563  $content = $submenu->writeMenu();
1564  // Restore the item count now that the submenu has been handled
1565  $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1566  $tsfe->register['count_menuItems'] = count($this->menuArr);
1567  return $content;
1568  }
1569  } catch (‪NoSuchMenuTypeException $e) {
1570  }
1571  }
1572  return '';
1573  }
1574 
1583  protected function ‪isNext($uid, $MPvar)
1584  {
1585  // Check for always active PIDs:
1586  if (in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1587  return true;
1588  }
1589  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1590  if ($uid && $testUid == $this->nextActive) {
1591  return true;
1592  }
1593  return false;
1594  }
1595 
1603  protected function ‪isActive($uid, $MPvar)
1604  {
1605  // Check for always active PIDs:
1606  if (in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1607  return true;
1608  }
1609  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1610  if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1611  return true;
1612  }
1613  return false;
1614  }
1615 
1623  protected function ‪isCurrent($uid, $MPvar)
1624  {
1625  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1626  return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1627  }
1628 
1636  protected function ‪isSubMenu($uid)
1637  {
1638  $cacheId = 'menucontentobject-is-submenu-decision-' . $uid . '-' . (int)($this->conf['includeNotInMenu'] ?? 0);
1639  $runtimeCache = $this->‪getRuntimeCache();
1640  $cachedDecision = $runtimeCache->get($cacheId);
1641  if (isset($cachedDecision['result'])) {
1642  return $cachedDecision['result'];
1643  }
1644  // Looking for a mount-pid for this UID since if that
1645  // exists we should look for a subpages THERE and not in the input $uid;
1646  $mount_info = $this->sys_page->getMountPointInfo($uid);
1647  if (is_array($mount_info)) {
1648  $uid = $mount_info['mount_pid'];
1649  }
1650  $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1651  $hasSubPages = false;
1652  $bannedUids = $this->‪getBannedUids();
1653  $languageId = $this->‪getCurrentLanguageAspect()->getId();
1654  foreach ($recs as $theRec) {
1655  // no valid subpage if the document type is excluded from the menu
1656  if (in_array((int)($theRec['doktype'] ?? 0), $this->excludedDoktypes, true)) {
1657  continue;
1658  }
1659  // No valid subpage if the page is hidden inside menus and
1660  // it wasn't forced to show such entries
1661  if (isset($theRec['nav_hide']) && $theRec['nav_hide']
1662  && (!isset($this->conf['includeNotInMenu']) || !$this->conf['includeNotInMenu'])
1663  ) {
1664  continue;
1665  }
1666  // No valid subpage if the default language should be shown and the page settings
1667  // are excluding the visibility of the default language
1668  if (!$languageId && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'] ?? 0)) {
1669  continue;
1670  }
1671  // No valid subpage if the alternative language should be shown and the page settings
1672  // are requiring a valid overlay but it doesn't exists
1673  $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg'] ?? null);
1674  if ($languageId && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1675  continue;
1676  }
1677  // No valid subpage if the subpage is banned by excludeUidList
1678  if (in_array((int)$theRec['uid'], $bannedUids, true)) {
1679  continue;
1680  }
1681  $hasSubPages = true;
1682  break;
1683  }
1684  $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1685  return $hasSubPages;
1686  }
1687 
1696  protected function ‪isItemState($kind, $key)
1697  {
1698  $natVal = false;
1699  // If any value is set for ITEM_STATE the normal evaluation is discarded
1700  if ($this->menuArr[$key]['ITEM_STATE'] ?? false) {
1701  if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1702  $natVal = true;
1703  }
1704  } else {
1705  switch ($kind) {
1706  case 'SPC':
1707  $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1708  break;
1709  case 'IFSUB':
1710  $natVal = $this->‪isSubMenu($this->menuArr[$key]['uid']);
1711  break;
1712  case 'ACT':
1713  $natVal = $this->‪isActive($this->menuArr[$key]['uid'], $this->‪getMPvar($key));
1714  break;
1715  case 'ACTIFSUB':
1716  $natVal = $this->‪isActive($this->menuArr[$key]['uid'], $this->‪getMPvar($key)) && $this->‪isSubMenu($this->menuArr[$key]['uid']);
1717  break;
1718  case 'CUR':
1719  $natVal = $this->‪isCurrent($this->menuArr[$key]['uid'], $this->‪getMPvar($key));
1720  break;
1721  case 'CURIFSUB':
1722  $natVal = $this->‪isCurrent($this->menuArr[$key]['uid'], $this->‪getMPvar($key)) && $this->‪isSubMenu($this->menuArr[$key]['uid']);
1723  break;
1724  case 'USR':
1725  $natVal = (bool)$this->menuArr[$key]['fe_group'];
1726  break;
1727  }
1728  }
1729  return $natVal;
1730  }
1731 
1738  protected function ‪accessKey($title)
1739  {
1740  $tsfe = $this->‪getTypoScriptFrontendController();
1741  // The global array ACCESSKEY is used to globally control if letters are already used!!
1742  $result = [];
1743  $title = trim(strip_tags($title));
1744  $titleLen = strlen($title);
1745  for ($a = 0; $a < $titleLen; $a++) {
1746  $key = strtoupper(substr($title, $a, 1));
1747  if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
1748  $tsfe->accessKey[$key] = true;
1749  ‪$result['code'] = ' accesskey="' . $key . '"';
1750  ‪$result['alt'] = ' (ALT+' . $key . ')';
1751  ‪$result['key'] = $key;
1752  break;
1753  }
1754  }
1755  return ‪$result;
1756  }
1757 
1766  protected function ‪userProcess($mConfKey, $passVar)
1767  {
1768  if ($this->mconf[$mConfKey]) {
1769  $funcConf = (array)($this->mconf[$mConfKey . '.'] ?? []);
1770  $funcConf['parentObj'] = $this;
1771  $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
1772  }
1773  return $passVar;
1774  }
1775 
1779  protected function ‪setATagParts()
1780  {
1781  $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
1782  $params = $params !== '' ? ' ' . $params : '';
1783  $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], true) . $params . '>';
1784  $this->I['A2'] = '</a>';
1785  }
1786 
1794  protected function ‪getPageTitle($title, $nav_title)
1795  {
1796  return trim($nav_title) !== '' ? $nav_title : $title;
1797  }
1798 
1806  protected function ‪getMPvar($key)
1807  {
1808  if (‪$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
1809  $localMP_array = ‪$this->MP_array;
1810  // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
1811  if ($this->menuArr[$key]['_MP_PARAM'] ?? false) {
1812  $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
1813  }
1814  return !empty($localMP_array) ? implode(',', $localMP_array) : '';
1815  }
1816  return '';
1817  }
1818 
1824  protected function ‪getDoktypeExcludeWhere()
1825  {
1826  return !empty($this->excludedDoktypes) ? ' AND pages.doktype NOT IN (' . implode(',', $this->excludedDoktypes) . ')' : '';
1827  }
1828 
1834  protected function ‪getBannedUids()
1835  {
1836  $excludeUidList = isset($this->conf['excludeUidList.'])
1837  ? $this->parent_cObj->stdWrap($this->conf['excludeUidList'], $this->conf['excludeUidList.'])
1838  : $this->conf['excludeUidList'];
1839 
1840  if (!trim($excludeUidList)) {
1841  return [];
1842  }
1843 
1844  $banUidList = str_replace('current', $this->‪getTypoScriptFrontendController()->page['uid'] ?? null, $excludeUidList);
1845  return ‪GeneralUtility::intExplode(',', $banUidList);
1846  }
1847 
1858  protected function ‪menuTypoLink($page, $oTarget, $addParams, $typeOverride, ?int $overridePageId = null)
1859  {
1860  ‪$conf = [
1861  'parameter' => $overridePageId ?? $page['uid']
1862  ];
1863  if (‪MathUtility::canBeInterpretedAsInteger($typeOverride)) {
1864  ‪$conf['parameter'] .= ',' . (int)$typeOverride;
1865  }
1866  if ($addParams) {
1867  ‪$conf['additionalParams'] = $addParams;
1868  }
1869 
1870  // Ensure that the typolink gets an info which language was actually requested. The $page record could be the record
1871  // from page translation language=1 as fallback but page translation language=2 was requested. Search for
1872  // "_PAGES_OVERLAY_REQUESTEDLANGUAGE" for more details
1873  if (isset($page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'])) {
1874  ‪$conf['language'] = $page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'];
1875  }
1876  if ($oTarget) {
1877  ‪$conf['target'] = $oTarget;
1878  }
1879  if ($page['sectionIndex_uid'] ?? false) {
1880  ‪$conf['section'] = $page['sectionIndex_uid'];
1881  }
1882  ‪$conf['linkAccessRestrictedPages'] = !empty($this->mconf['showAccessRestrictedPages']);
1883  $this->parent_cObj->typoLink('|', ‪$conf);
1884  $LD = $this->parent_cObj->lastTypoLinkLD;
1885  $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
1886  return $LD;
1887  }
1888 
1900  protected function ‪sectionIndex($altSortField, $pid = null)
1901  {
1902  $pid = (int)($pid ?: $this->id);
1903  $basePageRow = $this->sys_page->getPage($pid);
1904  if (!is_array($basePageRow)) {
1905  return [];
1906  }
1907  $tsfe = $this->‪getTypoScriptFrontendController();
1908  $configuration = $this->mconf['sectionIndex.'] ?? [];
1909  $useColPos = 0;
1910  if (trim($configuration['useColPos'] ?? '') !== ''
1911  || (isset($configuration['useColPos.']) && is_array($configuration['useColPos.']))
1912  ) {
1913  $useColPos = $tsfe->cObj->stdWrap($configuration['useColPos'] ?? '', $configuration['useColPos.'] ?? []);
1914  $useColPos = (int)$useColPos;
1915  }
1916  $selectSetup = [
1917  'pidInList' => $pid,
1918  'orderBy' => $altSortField,
1919  'languageField' => 'sys_language_uid',
1920  'where' => ''
1921  ];
1922 
1923  if ($useColPos >= 0) {
1924  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1925  ->getConnectionForTable('tt_content')
1926  ->getExpressionBuilder();
1927  $selectSetup['where'] = $expressionBuilder->eq('colPos', $useColPos);
1928  }
1929 
1930  if ($basePageRow['content_from_pid'] ?? false) {
1931  // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
1932  // the referenced page
1933  $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
1934  }
1935  $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
1936  if (!$statement) {
1937  $message = 'SectionIndex: Query to fetch the content elements failed!';
1938  throw new \UnexpectedValueException($message, 1337334849);
1939  }
1940  ‪$result = [];
1941  while ($row = $statement->fetch()) {
1942  $this->sys_page->versionOL('tt_content', $row);
1943  if ($this->‪getCurrentLanguageAspect()->doOverlays() && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
1944  $row = $this->sys_page->getRecordOverlay(
1945  'tt_content',
1946  $row,
1947  $basePageRow['_PAGES_OVERLAY_LANGUAGE'],
1948  $this->‪getCurrentLanguageAspect()->getOverlayType() === ‪LanguageAspect::OVERLAYS_MIXED ? '1' : 'hideNonTranslated'
1949  );
1950  }
1951  if ($this->mconf['sectionIndex.']['type'] !== 'all') {
1952  $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
1953  $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
1954  $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
1955  if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
1956  continue;
1957  }
1958  }
1959  if (is_array($row)) {
1960  $uid = $row['uid'] ?? null;
1961  ‪$result[$uid] = $basePageRow;
1962  ‪$result[$uid]['title'] = $row['header'];
1963  ‪$result[$uid]['nav_title'] = $row['header'];
1964  // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
1965  ‪$result[$uid]['nav_hide'] = 0;
1966  ‪$result[$uid]['subtitle'] = $row['subheader'] ?? '';
1967  ‪$result[$uid]['starttime'] = $row['starttime'] ?? '';
1968  ‪$result[$uid]['endtime'] = $row['endtime'] ?? '';
1969  ‪$result[$uid]['fe_group'] = $row['fe_group'] ?? '';
1970  ‪$result[$uid]['media'] = $row['media'] ?? '';
1971  ‪$result[$uid]['header_layout'] = $row['header_layout'] ?? '';
1972  ‪$result[$uid]['bodytext'] = $row['bodytext'] ?? '';
1973  ‪$result[$uid]['image'] = $row['image'] ?? '';
1974  ‪$result[$uid]['sectionIndex_uid'] = $uid;
1975  }
1976  }
1977 
1978  return ‪$result;
1979  }
1986  public function ‪getSysPage()
1987  {
1988  return ‪$this->sys_page;
1989  }
1990 
1996  public function ‪getParentContentObject()
1997  {
1998  return ‪$this->parent_cObj;
1999  }
2000 
2004  protected function ‪getTypoScriptFrontendController()
2005  {
2006  return ‪$GLOBALS['TSFE'];
2007  }
2008 
2010  {
2011  return GeneralUtility::makeInstance(Context::class)->getAspect('language');
2012  }
2013 
2017  protected function ‪getTimeTracker()
2018  {
2019  return GeneralUtility::makeInstance(TimeTracker::class);
2020  }
2021 
2025  protected function ‪getCache()
2026  {
2027  return GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
2028  }
2029 
2033  protected function ‪getRuntimeCache()
2034  {
2035  return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
2036  }
2043  protected function ‪getCurrentSite(): ‪SiteInterface
2044  {
2045  try {
2046  return GeneralUtility::makeInstance(SiteFinder::class)
2047  ->getSiteByPageId((int)$this->‪getTypoScriptFrontendController()->id);
2048  } catch (‪SiteNotFoundException $e) {
2049  return new ‪NullSite();
2050  }
2051  }
2052 
2061  public function ‪setParentMenu(array ‪$menuArr, $menuItemKey)
2062  {
2063  // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
2064  if (is_array(‪$menuArr)
2065  && (is_int($menuItemKey) && $menuItemKey >= 0 && isset(‪$menuArr[$menuItemKey]))
2066  ) {
2067  $this->parentMenuArr = ‪$menuArr;
2068  $this->parentMenuArrItemKey = $menuItemKey;
2069  }
2070  }
2071 
2077  protected function ‪hasParentMenuArr()
2078  {
2079  return
2080  $this->menuNumber > 1
2081  && is_array($this->parentMenuArr)
2082  && !empty($this->parentMenuArr)
2083  ;
2084  }
2085 
2089  protected function ‪hasParentMenuItemKey()
2090  {
2091  return null !== ‪$this->parentMenuArrItemKey;
2092  }
2093 
2097  protected function ‪hasParentMenuItem()
2098  {
2099  return
2100  $this->‪hasParentMenuArr()
2101  && $this->‪hasParentMenuItemKey()
2102  && isset($this->‪getParentMenuArr()[$this->parentMenuArrItemKey])
2103  ;
2104  }
2105 
2111  public function ‪getParentMenuArr()
2112  {
2113  return $this->‪hasParentMenuArr() ? $this->parentMenuArr : [];
2114  }
2115 
2121  public function ‪getParentMenuItem()
2122  {
2123  // check if we have a parentMenuItem and if it is an array
2124  if ($this->‪hasParentMenuItem()
2125  && is_array($this->‪getParentMenuArr()[$this->parentMenuArrItemKey])
2126  ) {
2128  }
2129 
2130  return null;
2131  }
2132 }
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getBannedUids
‪array getBannedUids()
Definition: AbstractMenuContentObject.php:1810
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getParentMenuItem
‪array null getParentMenuItem()
Definition: AbstractMenuContentObject.php:2097
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getTimeTracker
‪TimeTracker getTimeTracker()
Definition: AbstractMenuContentObject.php:1993
‪TYPO3\CMS\Core\Site\Entity\SiteInterface
Definition: SiteInterface.php:26
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$MP_array
‪string[] $MP_array
Definition: AbstractMenuContentObject.php:79
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMresult
‪string $WMresult
Definition: AbstractMenuContentObject.php:141
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$parentMenuArrItemKey
‪int null $parentMenuArrItemKey
Definition: AbstractMenuContentObject.php:165
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\hasParentMenuArr
‪bool hasParentMenuArr()
Definition: AbstractMenuContentObject.php:2053
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getPageTitle
‪string getPageTitle($title, $nav_title)
Definition: AbstractMenuContentObject.php:1770
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isNext
‪bool isNext($uid, $MPvar)
Definition: AbstractMenuContentObject.php:1559
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\writeMenu
‪string writeMenu()
Definition: AbstractMenuContentObject.php:451
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\menuTypoLink
‪array menuTypoLink($page, $oTarget, $addParams, $typeOverride, ?int $overridePageId=null)
Definition: AbstractMenuContentObject.php:1834
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$alternativeMenuTempArray
‪string $alternativeMenuTempArray
Definition: AbstractMenuContentObject.php:159
‪TYPO3\CMS\Core\Context\LanguageAspect\OVERLAYS_MIXED
‪const OVERLAYS_MIXED
Definition: LanguageAspect.php:75
‪TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException
Definition: NoSuchMenuTypeException.php:24
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\changeLinksForAccessRestrictedPages
‪changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
Definition: AbstractMenuContentObject.php:1474
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForRootlineMenu
‪array prepareMenuItemsForRootlineMenu()
Definition: AbstractMenuContentObject.php:1003
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isCurrent
‪bool isCurrent($uid, $MPvar)
Definition: AbstractMenuContentObject.php:1599
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$parentMenuArr
‪array $parentMenuArr
Definition: AbstractMenuContentObject.php:169
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\hasParentMenuItem
‪hasParentMenuItem()
Definition: AbstractMenuContentObject.php:2073
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$nextActive
‪string $nextActive
Definition: AbstractMenuContentObject.php:112
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:35
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$conf
‪array $conf
Definition: AbstractMenuContentObject.php:85
‪if
‪if(PHP_SAPI !=='cli')
Definition: splitAcceptanceTests.php:33
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItems
‪array prepareMenuItems()
Definition: AbstractMenuContentObject.php:479
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SHORTCUT
‪const DOKTYPE_SHORTCUT
Definition: PageRepository.php:105
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\sectionIndex
‪array sectionIndex($altSortField, $pid=null)
Definition: AbstractMenuContentObject.php:1876
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_LINK
‪const DOKTYPE_LINK
Definition: PageRepository.php:104
‪TYPO3\CMS\Core\Site\Entity\NullSite
Definition: NullSite.php:32
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForUpdatedMenu
‪array prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:778
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:26
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForLanguageMenu
‪array prepareMenuItemsForLanguageMenu($specialValue)
Definition: AbstractMenuContentObject.php:575
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getRuntimeCache
‪TYPO3 CMS Core Cache Frontend FrontendInterface getRuntimeCache()
Definition: AbstractMenuContentObject.php:2009
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\hasParentMenuItemKey
‪hasParentMenuItemKey()
Definition: AbstractMenuContentObject.php:2065
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject
Definition: AbstractMenuContentObject.php:46
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\SHORTCUT_MODE_RANDOM_SUBPAGE
‪const SHORTCUT_MODE_RANDOM_SUBPAGE
Definition: PageRepository.php:117
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getCurrentSite
‪SiteInterface getCurrentSite()
Definition: AbstractMenuContentObject.php:2019
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:53
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForUserSpecificMenu
‪array prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:559
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\filterMenuPages
‪bool filterMenuPages(&$data, $banUidArray, $isSpacerPage)
Definition: AbstractMenuContentObject.php:1178
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$result
‪array $result
Definition: AbstractMenuContentObject.php:126
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\userProcess
‪mixed userProcess($mConfKey, $passVar)
Definition: AbstractMenuContentObject.php:1742
‪TYPO3\CMS\Core\TimeTracker\TimeTracker\setTSlogMessage
‪setTSlogMessage($content, $num=0)
Definition: TimeTracker.php:215
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getDoktypeExcludeWhere
‪string getDoktypeExcludeWhere()
Definition: AbstractMenuContentObject.php:1800
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$sys_page
‪PageRepository $sys_page
Definition: AbstractMenuContentObject.php:99
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$I
‪mixed[] $I
Definition: AbstractMenuContentObject.php:137
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$menuArr
‪array[] $menuArr
Definition: AbstractMenuContentObject.php:118
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\generate
‪generate()
Definition: AbstractMenuContentObject.php:444
‪$languageItems
‪$languageItems
Definition: be_users.php:8
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$parent_cObj
‪ContentObjectRenderer $parent_cObj
Definition: AbstractMenuContentObject.php:73
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SPACER
‪const DOKTYPE_SPACER
Definition: PageRepository.php:108
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMsubmenuObjSuffixes
‪array[] $WMsubmenuObjSuffixes
Definition: AbstractMenuContentObject.php:149
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_BE_USER_SECTION
‪const DOKTYPE_BE_USER_SECTION
Definition: PageRepository.php:106
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:35
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$id
‪int $id
Definition: AbstractMenuContentObject.php:105
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuFilterPagesHookInterface
Definition: AbstractMenuFilterPagesHookInterface.php:22
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController\get_cache_timeout
‪int get_cache_timeout()
Definition: TypoScriptFrontendController.php:3563
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SYSFOLDER
‪const DOKTYPE_SYSFOLDER
Definition: PageRepository.php:109
‪TYPO3\CMS\Core\Context\LanguageAspect
Definition: LanguageAspect.php:57
‪TYPO3\CMS\Frontend\ContentObject\Menu
Definition: AbstractMenuContentObject.php:16
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isActive
‪bool isActive($uid, $MPvar)
Definition: AbstractMenuContentObject.php:1579
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForBrowseMenu
‪array prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
Definition: AbstractMenuContentObject.php:1059
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\makeMenu
‪makeMenu()
Definition: AbstractMenuContentObject.php:348
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\customItemStates
‪const customItemStates
Definition: AbstractMenuContentObject.php:171
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$entryLevel
‪int $entryLevel
Definition: AbstractMenuContentObject.php:57
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\setParentMenu
‪setParentMenu(array $menuArr, $menuItemKey)
Definition: AbstractMenuContentObject.php:2037
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Core\TypoScript\TypoScriptService
Definition: TypoScriptService.php:25
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getCurrentLanguageAspect
‪getCurrentLanguageAspect()
Definition: AbstractMenuContentObject.php:1985
‪TYPO3\CMS\Core\Resource\Exception
Definition: Exception.php:22
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$tmpl
‪TemplateService $tmpl
Definition: AbstractMenuContentObject.php:95
‪TYPO3\CMS\Core\TypoScript\TemplateService
Definition: TemplateService.php:46
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\removeInaccessiblePages
‪array removeInaccessiblePages(array $pages)
Definition: AbstractMenuContentObject.php:462
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMmenuItems
‪int $WMmenuItems
Definition: AbstractMenuContentObject.php:145
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$rL_uidRegister
‪array $rL_uidRegister
Definition: AbstractMenuContentObject.php:133
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
Definition: TypoScriptFrontendController.php:98
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\start
‪bool start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix='')
Definition: AbstractMenuContentObject.php:199
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getTypoScriptFrontendController
‪TypoScriptFrontendController getTypoScriptFrontendController()
Definition: AbstractMenuContentObject.php:1980
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$menuNumber
‪int $menuNumber
Definition: AbstractMenuContentObject.php:51
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForKeywordsMenu
‪array prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:868
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:988
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\determineOriginalShortcutPage
‪array determineOriginalShortcutPage(array $page)
Definition: AbstractMenuContentObject.php:1445
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
Definition: ContentObjectRenderer.php:97
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getParentContentObject
‪ContentObjectRenderer getParentContentObject()
Definition: AbstractMenuContentObject.php:1972
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$hash
‪string $hash
Definition: AbstractMenuContentObject.php:122
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\processItemStates
‪array processItemStates($splitCount)
Definition: AbstractMenuContentObject.php:1241
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$excludedDoktypes
‪int[] $excludedDoktypes
Definition: AbstractMenuContentObject.php:63
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:52
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$mconf
‪array $mconf
Definition: AbstractMenuContentObject.php:91
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getSysPage
‪PageRepository getSysPage()
Definition: AbstractMenuContentObject.php:1962
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$alwaysActivePIDlist
‪int[] $alwaysActivePIDlist
Definition: AbstractMenuContentObject.php:67
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\setATagParts
‪setATagParts()
Definition: AbstractMenuContentObject.php:1755
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getParentMenuArr
‪array getParentMenuArr()
Definition: AbstractMenuContentObject.php:2087
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:30
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\accessKey
‪array accessKey($title)
Definition: AbstractMenuContentObject.php:1714
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForDirectoryMenu
‪array prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:640
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getMPvar
‪string getMPvar($key)
Definition: AbstractMenuContentObject.php:1782
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMcObj
‪ContentObjectRenderer $WMcObj
Definition: AbstractMenuContentObject.php:153
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getCache
‪TYPO3 CMS Core Cache Frontend FrontendInterface getCache()
Definition: AbstractMenuContentObject.php:2001
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isItemState
‪bool isItemState($kind, $key)
Definition: AbstractMenuContentObject.php:1672
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isSubMenu
‪bool isSubMenu($uid)
Definition: AbstractMenuContentObject.php:1612
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\link
‪array link($key, $altTarget, $typeOverride)
Definition: AbstractMenuContentObject.php:1283
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\subMenu
‪string subMenu($uid, $objSuffix)
Definition: AbstractMenuContentObject.php:1501