‪TYPO3CMS  11.5
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 
18 use Psr\Http\Message\ServerRequestInterface;
19 use Psr\Log\LogLevel;
34 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
39 
48 {
50 
56  protected ‪$menuNumber = 1;
57 
63  protected ‪$entryLevel = 0;
64 
71 
75  protected ‪$alwaysActivePIDlist = [];
76 
83 
89  protected ‪$MP_array = [];
90 
96  protected ‪$conf = [];
97 
103  protected ‪$mconf = [];
104 
108  protected ‪$tmpl;
109 
113  protected ‪$sys_page;
114 
120  protected ‪$id;
121 
128  protected ‪$nextActive;
129 
135  protected ‪$menuArr;
136 
140  protected ‪$hash;
141 
145  protected ‪$result = [];
146 
153  protected ‪$rL_uidRegister;
154 
158  protected ‪$I;
159 
163  protected ‪$WMresult;
164 
168  protected ‪$WMmenuItems;
169 
173  protected ‪$WMsubmenuObjSuffixes;
174 
178  protected ‪$WMcObj;
179 
180  protected ?ServerRequestInterface ‪$request = null;
181 
187  protected ‪$alternativeMenuTempArray = [];
188 
194  protected ‪$parentMenuArrItemKey;
195 
199  protected ‪$parentMenuArr;
200 
201  protected const ‪customItemStates = [
202  // IFSUB is TRUE if there exist submenu items to the current item
203  'IFSUB',
204  'ACT',
205  // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
206  'ACTIFSUB',
207  // CUR is TRUE if the current page equals the item here!
208  'CUR',
209  // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
210  'CURIFSUB',
211  'USR',
212  'SPC',
213  'USERDEF1',
214  'USERDEF2',
215  ];
216 
230  public function ‪start(‪$tmpl, ‪$sys_page, ‪$id, ‪$conf, ‪$menuNumber, $objSuffix = '', ?ServerRequestInterface ‪$request = null)
231  {
232  $tsfe = $this->‪getTypoScriptFrontendController();
233  $this->conf = ‪$conf;
234  $this->menuNumber = ‪$menuNumber;
235  $this->mconf = ‪$conf[$this->menuNumber . $objSuffix . '.'];
236  $this->request = ‪$request;
237  $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
238  // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the PageRepository object
239  if ($this->conf[$this->menuNumber . $objSuffix] && is_object(‪$tmpl) && is_object(‪$sys_page)) {
240  $this->tmpl = ‪$tmpl;
241  $this->sys_page = ‪$sys_page;
242  // alwaysActivePIDlist initialized:
243  $this->conf['alwaysActivePIDlist'] = (string)$this->parent_cObj->stdWrapValue('alwaysActivePIDlist', $this->conf ?? []);
244  if (trim($this->conf['alwaysActivePIDlist'])) {
245  $this->alwaysActivePIDlist = ‪GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
246  }
247  // includeNotInMenu initialized:
248  $this->conf['includeNotInMenu'] = $this->parent_cObj->stdWrapValue('includeNotInMenu', $this->conf, false);
249  // exclude doktypes that should not be shown in menu (e.g. backend user section)
250  if ($this->conf['excludeDoktypes'] ?? false) {
251  $this->excludedDoktypes = ‪GeneralUtility::intExplode(',', (string)($this->conf['excludeDoktypes']));
252  }
253  // EntryLevel
254  $this->entryLevel = $this->parent_cObj->getKey(
255  $this->parent_cObj->stdWrapValue('entryLevel', $this->conf ?? []),
256  $this->tmpl->rootLine
257  );
258  // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
259  // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
260  if (‪$id) {
261  $this->id = (int)‪$id;
262  } else {
263  // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
264  $this->id = (int)($this->tmpl->rootLine[$this->entryLevel]['uid'] ?? 0);
265 
266  // Traverse rootline to build MP_array of pages BEFORE the entryLevel
267  // (MP var for ->id is picked up in the next part of the code...)
268  foreach ($this->tmpl->rootLine as ‪$entryLevel => $levelRec) {
269  // For overlaid mount points, set the variable right now:
270  if (($levelRec['_MP_PARAM'] ?? false) && ($levelRec['_MOUNT_OL'] ?? false)) {
271  $this->MP_array[] = $levelRec['_MP_PARAM'];
272  }
273 
274  // Break when entry level is reached:
275  if (‪$entryLevel >= $this->entryLevel) {
276  break;
277  }
278 
279  // For normal mount points, set the variable for next level.
280  if (!empty($levelRec['_MP_PARAM']) && empty($levelRec['_MOUNT_OL'])) {
281  $this->MP_array[] = $levelRec['_MP_PARAM'];
282  }
283  }
284  }
285  // Return FALSE if no page ID was set (thus no menu of subpages can be made).
286  if ($this->id <= 0) {
287  return false;
288  }
289  // Check if page is a mount point, and if so set id and MP_array
290  // (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...)
291  $mount_info = $this->sys_page->getMountPointInfo($this->id);
292  if (is_array($mount_info)) {
293  $this->MP_array[] = $mount_info['MPvar'];
294  $this->id = $mount_info['mount_pid'];
295  }
296  // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
297  // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
298  if ($this->rL_uidRegister === null) {
299  $this->rL_uidRegister = [];
300  $rl_MParray = [];
301  foreach ($this->tmpl->rootLine as $v_rl) {
302  // For overlaid mount points, set the variable right now:
303  if (($v_rl['_MP_PARAM'] ?? false) && ($v_rl['_MOUNT_OL'] ?? false)) {
304  $rl_MParray[] = $v_rl['_MP_PARAM'];
305  }
306  // Add to register:
307  $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
308  (
309  !empty($rl_MParray)
310  ? ':' . implode(',', $rl_MParray)
311  : ''
312  );
313  // For normal mount points, set the variable for next level.
314  if (($v_rl['_MP_PARAM'] ?? false) && !($v_rl['_MOUNT_OL'] ?? false)) {
315  $rl_MParray[] = $v_rl['_MP_PARAM'];
316  }
317  }
318  }
319  // Set $directoryLevel so the following evaluation of the nextActive will not return
320  // an invalid value if .special=directory was set
321  $directoryLevel = 0;
322  if (($this->conf['special'] ?? '') === 'directory') {
323  $value = $this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
324  if ($value === '') {
325  $value = (string)$tsfe->id;
326  }
327  $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
328  }
329  // Setting "nextActive": This is the page uid + MPvar of the NEXT page in rootline. Used to expand the menu if we are in the right branch of the tree
330  // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
331  $startLevel = $directoryLevel ?: ‪$this->entryLevel;
332  $currentLevel = $startLevel + ‪$this->menuNumber;
333  if (is_array($this->tmpl->rootLine[$currentLevel] ?? null)) {
334  $nextMParray = ‪$this->MP_array;
335  if (empty($nextMParray) && !($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] ?? false) && $currentLevel > 0) {
336  // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
337  // otherwise automatic expansion will not work
338  $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
339  if (isset($parentRecord['_MP_PARAM'])) {
340  $nextMParray[] = $parentRecord['_MP_PARAM'];
341  }
342  }
343  // In overlay mode, add next level MPvars as well:
344  if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] ?? false) {
345  $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
346  }
347  $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
348  (
349  !empty($nextMParray)
350  ? ':' . implode(',', $nextMParray)
351  : ''
352  );
353  } else {
354  $this->nextActive = '';
355  }
356  return true;
357  }
358  $this->‪getTimeTracker()->‪setTSlogMessage('ERROR in menu', LogLevel::ERROR);
359  return false;
360  }
361 
368  public function ‪makeMenu()
369  {
370  if (!$this->id) {
371  return;
372  }
373 
374  $frontendController = $this->‪getTypoScriptFrontendController();
375  // Initializing showAccessRestrictedPages
376  $SAVED_where_groupAccess = '';
377  if ($this->mconf['showAccessRestrictedPages'] ?? false) {
378  // SAVING where_groupAccess
379  $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
380  // Temporarily removing fe_group checking!
381  $this->sys_page->where_groupAccess = '';
382  }
383 
384  $menuItems = $this->‪prepareMenuItems();
385 
386  $c = 0;
387  $c_b = 0;
388 
389  $minItems = (int)(($this->mconf['minItems'] ?? 0) ?: ($this->conf['minItems'] ?? 0));
390  $maxItems = (int)(($this->mconf['maxItems'] ?? 0) ?: ($this->conf['maxItems'] ?? 0));
391  $begin = $this->parent_cObj->calc(($this->mconf['begin'] ?? 0) ?: ($this->conf['begin'] ?? 0));
392  $minItemsConf = $this->mconf['minItems.'] ?? $this->conf['minItems.'] ?? null;
393  $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
394  $maxItemsConf = $this->mconf['maxItems.'] ?? $this->conf['maxItems.'] ?? null;
395  $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
396  $beginConf = $this->mconf['begin.'] ?? $this->conf['begin.'] ?? null;
397  $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
398  $banUidArray = $this->‪getBannedUids();
399  // Fill in the menuArr with elements that should go into the menu:
400  $this->menuArr = [];
401  foreach ($menuItems as $data) {
402  $isSpacerPage = (int)($data['doktype'] ?? 0) === ‪PageRepository::DOKTYPE_SPACER || ($data['ITEM_STATE'] ?? '') === 'SPC';
403  // if item is a spacer, $spacer is set
404  if ($this->‪filterMenuPages($data, $banUidArray, $isSpacerPage)) {
405  $c_b++;
406  // If the beginning item has been reached.
407  if ($begin <= $c_b) {
408  $this->menuArr[$c] = $this->‪determineOriginalShortcutPage($data);
409  $this->menuArr[$c]['isSpacer'] = $isSpacerPage;
410  $c++;
411  if ($maxItems && $c >= $maxItems) {
412  break;
413  }
414  }
415  }
416  }
417  // Fill in fake items, if min-items is set.
418  if ($minItems) {
419  while ($c < $minItems) {
420  $this->menuArr[$c] = [
421  'title' => '...',
422  'uid' => $frontendController->id,
423  ];
424  $c++;
425  }
426  }
427  // Passing the menuArr through a user defined function:
428  if ($this->mconf['itemArrayProcFunc'] ?? false) {
429  $this->menuArr = $this->‪userProcess('itemArrayProcFunc', $this->menuArr);
430  }
431  // Setting number of menu items
432  $frontendController->register['count_menuItems'] = count($this->menuArr);
433  $this->hash = md5(
434  json_encode($this->menuArr) .
435  json_encode($this->mconf) .
436  json_encode($this->tmpl->rootLine) .
437  json_encode($this->MP_array)
438  );
439  // Get the cache timeout:
440  if ($this->conf['cache_period'] ?? false) {
441  $cacheTimeout = $this->conf['cache_period'];
442  } else {
443  $cacheTimeout = $frontendController->get_cache_timeout();
444  }
445  $cache = $this->‪getCache();
446  $cachedData = $cache->get($this->hash);
447  if (!is_array($cachedData)) {
448  $this->‪generate();
449  $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
450  } else {
451  $this->result = $cachedData;
452  }
453  // End showAccessRestrictedPages
454  if ($this->mconf['showAccessRestrictedPages'] ?? false) {
455  // RESTORING where_groupAccess
456  $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
457  }
458  }
459 
465  public function ‪generate() {}
466 
470  public function ‪writeMenu()
471  {
472  return '';
473  }
474 
481  protected function ‪removeInaccessiblePages(array $pages)
482  {
483  $banned = $this->‪getBannedUids();
484  $filteredPages = [];
485  foreach ($pages as $aPage) {
486  if ($this->‪filterMenuPages($aPage, $banned, (int)$aPage['doktype'] === ‪PageRepository::DOKTYPE_SPACER)) {
487  $filteredPages[$aPage['uid']] = $aPage;
488  }
489  }
490  return $filteredPages;
491  }
492 
498  protected function ‪prepareMenuItems()
499  {
500  $menuItems = [];
501  $alternativeSortingField = trim($this->mconf['alternativeSortingField'] ?? '') ?: 'sorting';
502 
503  // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
504  $additionalWhere = $this->parent_cObj->stdWrapValue('additionalWhere', $this->mconf ?? []);
505  $additionalWhere .= $this->‪getDoktypeExcludeWhere();
506 
507  // ... only for the FIRST level of a HMENU
508  if ($this->menuNumber == 1 && ($this->conf['special'] ?? false)) {
509  $value = (string)$this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
510  switch ($this->conf['special']) {
511  case 'userfunction':
512  $menuItems = $this->‪prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
513  break;
514  case 'language':
515  $menuItems = $this->‪prepareMenuItemsForLanguageMenu($value);
516  break;
517  case 'directory':
518  $menuItems = $this->‪prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
519  break;
520  case 'list':
521  $menuItems = $this->‪prepareMenuItemsForListMenu($value);
522  break;
523  case 'updated':
524  $menuItems = $this->‪prepareMenuItemsForUpdatedMenu(
525  $value,
526  $this->mconf['alternativeSortingField'] ?? ''
527  );
528  break;
529  case 'keywords':
530  $menuItems = $this->‪prepareMenuItemsForKeywordsMenu(
531  $value,
532  $this->mconf['alternativeSortingField'] ?? ''
533  );
534  break;
535  case 'categories':
536  $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
537  $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
538  break;
539  case 'rootline':
540  $menuItems = $this->‪prepareMenuItemsForRootlineMenu();
541  break;
542  case 'browse':
543  $menuItems = $this->‪prepareMenuItemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
544  break;
545  }
546  if ($this->mconf['sectionIndex'] ?? false) {
547  $sectionIndexes = [];
548  foreach ($menuItems as $page) {
549  $sectionIndexes = $sectionIndexes + $this->‪sectionIndex($alternativeSortingField, $page['uid']);
550  }
551  $menuItems = $sectionIndexes;
552  }
553  } elseif ($this->alternativeMenuTempArray !== []) {
554  // Setting $menuItems array if not level 1.
556  } elseif ($this->mconf['sectionIndex'] ?? false) {
557  $menuItems = $this->‪sectionIndex($alternativeSortingField);
558  } else {
559  // Default: Gets a hierarchical menu based on subpages of $this->id
560  $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
561  }
562  return $menuItems;
563  }
564 
572  protected function ‪prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
573  {
574  $menuItems = $this->parent_cObj->callUserFunction(
575  $this->conf['special.']['userFunc'],
576  array_merge($this->conf['special.'], ['value' => $specialValue, '_altSortField' => $sortingField]),
577  ''
578  );
579  return is_array($menuItems) ? $menuItems : [];
580  }
581 
588  protected function ‪prepareMenuItemsForLanguageMenu($specialValue)
589  {
590  $menuItems = [];
591  // Getting current page record NOT overlaid by any translation:
592  $tsfe = $this->‪getTypoScriptFrontendController();
593  $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->id);
594 
595  if ($specialValue === 'auto') {
596  $site = $this->‪getCurrentSite();
597  $languages = $site->getLanguages();
598  $languageItems = array_keys($languages);
599  } else {
600  $languageItems = ‪GeneralUtility::intExplode(',', $specialValue);
601  }
602 
603  $tsfe->register['languages_HMENU'] = implode(',', $languageItems);
604 
605  $currentLanguageId = $this->‪getCurrentLanguageAspect()->getId();
606 
607  foreach ($languageItems as $sUid) {
608  // Find overlay record:
609  if ($sUid) {
610  $lRecs = $this->sys_page->getPageOverlay($currentPageWithNoOverlay, $sUid);
611  // getPageOverlay() might return the original record again, if so this is emptied
612  // this should be fixed in PageRepository in the future.
613  if (!empty($lRecs) && !isset($lRecs['_PAGES_OVERLAY'])) {
614  $lRecs = [];
615  }
616  } else {
617  $lRecs = [];
618  }
619  // Checking if the "disabled" state should be set.
620  $pageTranslationVisibility = new PageTranslationVisibility((int)($currentPageWithNoOverlay['l18n_cfg'] ?? 0));
621  if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() && $sUid &&
622  empty($lRecs) || $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() &&
623  (!$sUid || empty($lRecs)) ||
624  !($this->conf['special.']['normalWhenNoLanguage'] ?? false) && $sUid && empty($lRecs)
625  ) {
626  $iState = $currentLanguageId === $sUid ? 'USERDEF2' : 'USERDEF1';
627  } else {
628  $iState = $currentLanguageId === $sUid ? 'ACT' : 'NO';
629  }
630  // Adding menu item:
631  $menuItems[] = array_merge(
632  array_merge($currentPageWithNoOverlay, $lRecs),
633  [
634  '_PAGES_OVERLAY_REQUESTEDLANGUAGE' => $sUid,
635  'ITEM_STATE' => $iState,
636  '_ADD_GETVARS' => $this->conf['addQueryString'] ?? false,
637  '_SAFE' => true,
638  ]
639  );
640  }
641  return $menuItems;
642  }
643 
651  protected function ‪prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
652  {
653  $tsfe = $this->‪getTypoScriptFrontendController();
654  $menuItems = [];
655  if ($specialValue == '') {
656  $specialValue = $tsfe->id;
657  }
658  $items = ‪GeneralUtility::intExplode(',', (string)$specialValue);
659  $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
660  foreach ($items as ‪$id) {
661  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps(‪$id);
662  // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
663  $mount_info = $this->sys_page->getMountPointInfo(‪$id);
664  if (is_array($mount_info)) {
665  if ($mount_info['overlay']) {
666  // Overlays should already have their full MPvars calculated:
667  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid']);
668  $MP = $MP ?: $mount_info['MPvar'];
669  } else {
670  $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
671  }
672  ‪$id = $mount_info['mount_pid'];
673  }
674  $subPages = $this->sys_page->getMenu(‪$id, '*', $sortingField);
675  foreach ($subPages as $row) {
676  // Add external MP params
677  if ($MP) {
678  $row['_MP_PARAM'] = $MP . (($row['_MP_PARAM'] ?? '') ? ',' . $row['_MP_PARAM'] : '');
679  }
680  $menuItems[] = $row;
681  }
682  }
683 
684  return $menuItems;
685  }
686 
693  protected function ‪prepareMenuItemsForListMenu($specialValue)
694  {
695  $menuItems = [];
696  if ($specialValue == '') {
697  $specialValue = ‪$this->id;
698  }
699  $pageIds = ‪GeneralUtility::intExplode(',', (string)$specialValue);
700  $disableGroupAccessCheck = !empty($this->mconf['showAccessRestrictedPages']);
701  $pageRecords = $this->sys_page->getMenuForPages($pageIds);
702  // After fetching the page records, restore the initial order by using the page id list as arrays keys and
703  // replace them with the resolved page records. The id list is cleaned up first, since ids might be invalid.
704  $pageRecords = array_replace(
705  array_flip(array_intersect(array_values($pageIds), array_keys($pageRecords))),
706  $pageRecords
707  );
708  $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
709  foreach ($pageRecords as $row) {
710  $pageId = (int)$row['uid'];
711  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($pageId);
712  // Keep mount point?
713  $mount_info = $this->sys_page->getMountPointInfo($pageId, $row);
714  // $pageId is a valid mount point
715  if (is_array($mount_info) && $mount_info['overlay']) {
716  $mountedPageId = (int)$mount_info['mount_pid'];
717  // Using "getPage" is OK since we need the check for enableFields
718  // AND for type 2 of mount pids we DO require a doktype < 200!
719  $mountedPageRow = $this->sys_page->getPage($mountedPageId, $disableGroupAccessCheck);
720  if (empty($mountedPageRow)) {
721  // If the mount point could not be fetched with respect to
722  // enableFields, the page should not become a part of the menu!
723  continue;
724  }
725  $row = $mountedPageRow;
726  $row['_MP_PARAM'] = $mount_info['MPvar'];
727  // Overlays should already have their full MPvars calculated, that's why we unset the
728  // existing $row['_MP_PARAM'], as the full $MP will be added again below
729  $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($mountedPageId);
730  if ($MP) {
731  unset($row['_MP_PARAM']);
732  }
733  }
734  if ($MP) {
735  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
736  }
737  $menuItems[] = $row;
738  }
739  return $menuItems;
740  }
741 
749  protected function ‪prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
750  {
751  $tsfe = $this->‪getTypoScriptFrontendController();
752  $menuItems = [];
753  if ($specialValue == '') {
754  $specialValue = $tsfe->id;
755  }
756  $items = ‪GeneralUtility::intExplode(',', (string)$specialValue);
757  if (‪MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'] ?? null)) {
758  $depth = ‪MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
759  } else {
760  $depth = 20;
761  }
762  // Max number of items
763  $limit = ‪MathUtility::forceIntegerInRange(($this->conf['special.']['limit'] ?? 0), 0, 100);
764  $maxAge = (int)($this->parent_cObj->calc($this->conf['special.']['maxAge'] ?? 0));
765  if (!$limit) {
766  $limit = 10;
767  }
768  // 'auto', 'manual', 'tstamp'
769  $mode = $this->conf['special.']['mode'] ?? '';
770  // Get id's
771  $beginAtLevel = ‪MathUtility::forceIntegerInRange(($this->conf['special.']['beginAtLevel'] ?? 0), 0, 100);
772  $id_list_arr = [];
773  foreach ($items as ‪$id) {
774  // Exclude the current ID if beginAtLevel is > 0
775  if ($beginAtLevel > 0) {
776  $id_list_arr[] = $this->parent_cObj->getTreeList(‪$id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
777  } else {
778  $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * ‪$id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
779  }
780  }
781  $id_list = implode(',', $id_list_arr);
782  $pageIds = ‪GeneralUtility::intExplode(',', $id_list);
783  // Get sortField (mode)
784  $sortField = $this->‪getMode($mode);
785 
786  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
787  $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->‪getDoktypeExcludeWhere();
788  if ($this->conf['special.']['excludeNoSearchPages'] ?? false) {
789  $extraWhere .= sprintf(' AND %s=%s', $connection->quoteIdentifier('pages.no_search'), $connection->quote(0, ‪Connection::PARAM_INT));
790  }
791  if ($maxAge > 0) {
792  $extraWhere .= sprintf(' AND %s>%s', $connection->quoteIdentifier($sortField), $connection->quote((‪$GLOBALS['SIM_ACCESS_TIME'] - $maxAge), ‪Connection::PARAM_INT));
793  }
794  $extraWhere = sprintf('%s>=%s', $connection->quoteIdentifier($sortField), $connection->quote(0, ‪Connection::PARAM_INT)) . $extraWhere;
795 
796  $i = 0;
797  $pageRecords = $this->sys_page->getMenuForPages($pageIds, '*', $sortingField ?: $sortField . ' DESC', $extraWhere);
798  foreach ($pageRecords as $row) {
799  // Build a custom LIMIT clause as "getMenuForPages()" does not support this
800  if (++$i > $limit) {
801  continue;
802  }
803  $menuItems[$row['uid']] = $row;
804  }
805 
806  return $menuItems;
807  }
808 
816  protected function ‪prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
817  {
818  $tsfe = $this->‪getTypoScriptFrontendController();
819  $menuItems = [];
820  [$specialValue] = ‪GeneralUtility::intExplode(',', $specialValue);
821  if (!$specialValue) {
822  $specialValue = $tsfe->id;
823  }
824  if (($this->conf['special.']['setKeywords'] ?? false) || ($this->conf['special.']['setKeywords.'] ?? false)) {
825  $kw = (string)$this->parent_cObj->stdWrapValue('setKeywords', $this->conf['special.'] ?? []);
826  } else {
827  // The page record of the 'value'.
828  $value_rec = $this->sys_page->getPage($specialValue);
829  $kfieldSrc = ($this->conf['special.']['keywordsField.']['sourceField'] ?? false) ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
830  // keywords.
831  $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc] ?? ''));
832  }
833  // *'auto', 'manual', 'tstamp'
834  $mode = $this->conf['special.']['mode'] ?? '';
835  $sortField = $this->‪getMode($mode);
836  // Depth, limit, extra where
837  if (‪MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'] ?? null)) {
838  $depth = ‪MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
839  } else {
840  $depth = 20;
841  }
842  // Max number of items
843  $limit = ‪MathUtility::forceIntegerInRange(($this->conf['special.']['limit'] ?? 0), 0, 100);
844  // Start point
845  $eLevel = $this->parent_cObj->getKey(
846  $this->parent_cObj->stdWrapValue('entryLevel', $this->conf['special.'] ?? []),
847  $this->tmpl->rootLine
848  );
849  $startUid = (int)($this->tmpl->rootLine[$eLevel]['uid'] ?? 0);
850  // Which field is for keywords
851  $kfield = 'keywords';
852  if ($this->conf['special.']['keywordsField'] ?? false) {
853  [$kfield] = explode(' ', trim($this->conf['special.']['keywordsField']));
854  }
855  // If there are keywords and the startuid is present
856  if ($kw && $startUid) {
857  $bA = ‪MathUtility::forceIntegerInRange(($this->conf['special.']['beginAtLevel'] ?? 0), 0, 100);
858  $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
859  $kwArr = ‪GeneralUtility::trimExplode(',', $kw, true);
860  $keyWordsWhereArr = [];
861  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
862  foreach ($kwArr as $word) {
863  $keyWordsWhereArr[] = $queryBuilder->expr()->like(
864  $kfield,
865  $queryBuilder->createNamedParameter(
866  '%' . $queryBuilder->escapeLikeWildcards($word) . '%',
868  )
869  );
870  }
871  $queryBuilder
872  ->select('*')
873  ->from('pages')
874  ->where(
875  $queryBuilder->expr()->in(
876  'uid',
877  ‪GeneralUtility::intExplode(',', $id_list, true)
878  ),
879  $queryBuilder->expr()->neq(
880  'uid',
881  $queryBuilder->createNamedParameter($specialValue, ‪Connection::PARAM_INT)
882  )
883  );
884 
885  if (!empty($keyWordsWhereArr)) {
886  $queryBuilder->andWhere($queryBuilder->expr()->orX(...$keyWordsWhereArr));
887  }
888 
889  if (!empty($this->excludedDoktypes)) {
890  $queryBuilder->andWhere(
891  $queryBuilder->expr()->notIn(
892  'pages.doktype',
893  $this->excludedDoktypes
894  )
895  );
896  }
897 
898  if (!$this->conf['includeNotInMenu']) {
899  $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.nav_hide', 0));
900  }
901 
902  if ($this->conf['special.']['excludeNoSearchPages'] ?? false) {
903  $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.no_search', 0));
904  }
905 
906  if ($limit > 0) {
907  $queryBuilder->setMaxResults($limit);
908  }
909 
910  if ($sortingField) {
911  $queryBuilder->orderBy($sortingField);
912  } else {
913  $queryBuilder->orderBy($sortField, 'desc');
914  }
915 
916  ‪$result = $queryBuilder->executeQuery();
917  while ($row = ‪$result->fetchAssociative()) {
918  $this->sys_page->versionOL('pages', $row, true);
919  if (is_array($row)) {
920  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
921  }
922  }
923  }
924 
925  return $menuItems;
926  }
927 
933  protected function ‪prepareMenuItemsForRootlineMenu()
934  {
935  $menuItems = [];
936  $range = (string)$this->parent_cObj->stdWrapValue('range', $this->conf['special.'] ?? []);
937  $begin_end = explode('|', $range);
938  $begin_end[0] = (int)$begin_end[0];
939  if (!‪MathUtility::canBeInterpretedAsInteger($begin_end[1] ?? '')) {
940  $begin_end[1] = -1;
941  }
942  $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
943  $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
944  if ($endKey < $beginKey) {
945  $endKey = $beginKey;
946  }
947  $rl_MParray = [];
948  foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
949  // For overlaid mount points, set the variable right now:
950  if (($v_rl['_MP_PARAM'] ?? false) && ($v_rl['_MOUNT_OL'] ?? false)) {
951  $rl_MParray[] = $v_rl['_MP_PARAM'];
952  }
953  // Traverse rootline:
954  if ($k_rl >= $beginKey && $k_rl <= $endKey) {
955  $temp_key = $k_rl;
956  $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
957  if (!empty($menuItems[$temp_key])) {
958  // If there are no specific target for the page, put the level specific target on.
959  if (!$menuItems[$temp_key]['target']) {
960  $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl] ?? '';
961  $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
962  }
963  } else {
964  unset($menuItems[$temp_key]);
965  }
966  }
967  // For normal mount points, set the variable for next level.
968  if (($v_rl['_MP_PARAM'] ?? false) && !($v_rl['_MOUNT_OL'] ?? false)) {
969  $rl_MParray[] = $v_rl['_MP_PARAM'];
970  }
971  }
972  // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
973  if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
974  $menuItems = array_reverse($menuItems);
975  }
976  return $menuItems;
977  }
978 
987  protected function ‪prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
988  {
989  $menuItems = [];
990  [$specialValue] = ‪GeneralUtility::intExplode(',', $specialValue);
991  if (!$specialValue) {
992  $specialValue = $this->‪getTypoScriptFrontendController()->page['uid'];
993  }
994  // Will not work out of rootline
995  if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
996  $recArr = [];
997  // The page record of the 'value'.
998  $value_rec = $this->sys_page->getPage($specialValue);
999  // 'up' page cannot be outside rootline
1000  if ($value_rec['pid']) {
1001  // The page record of 'up'.
1002  $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1003  }
1004  // If the 'up' item was NOT level 0 in rootline...
1005  if (($recArr['up']['pid'] ?? 0) && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1006  // The page record of "index".
1007  $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1008  }
1009  // check if certain pages should be excluded
1010  $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->‪getDoktypeExcludeWhere();
1011  if ($this->conf['special.']['excludeNoSearchPages'] ?? false) {
1012  $additionalWhere .= ' AND pages.no_search=0';
1013  }
1014  // prev / next is found
1015  $prevnext_menu = $this->‪removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1016  $lastKey = 0;
1017  ‪$nextActive = 0;
1018  foreach ($prevnext_menu as $k_b => $v_b) {
1019  if (‪$nextActive) {
1020  $recArr['next'] = $v_b;
1021  ‪$nextActive = 0;
1022  }
1023  if ($v_b['uid'] == $specialValue) {
1024  if ($lastKey) {
1025  $recArr['prev'] = $prevnext_menu[$lastKey];
1026  }
1027  ‪$nextActive = 1;
1028  }
1029  $lastKey = $k_b;
1030  }
1031 
1032  $recArr['first'] = reset($prevnext_menu);
1033  $recArr['last'] = end($prevnext_menu);
1034  // prevsection / nextsection is found
1035  // You can only do this, if there is a valid page two levels up!
1036  if (!empty($recArr['index']['uid'])) {
1037  $prevnextsection_menu = $this->‪removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1038  $lastKey = 0;
1039  ‪$nextActive = 0;
1040  foreach ($prevnextsection_menu as $k_b => $v_b) {
1041  if (‪$nextActive) {
1042  $sectionRec_temp = $this->‪removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1043  if (!empty($sectionRec_temp)) {
1044  $recArr['nextsection'] = reset($sectionRec_temp);
1045  $recArr['nextsection_last'] = end($sectionRec_temp);
1046  ‪$nextActive = 0;
1047  }
1048  }
1049  if ($v_b['uid'] == $value_rec['pid']) {
1050  if ($lastKey) {
1051  $sectionRec_temp = $this->‪removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1052  if (!empty($sectionRec_temp)) {
1053  $recArr['prevsection'] = reset($sectionRec_temp);
1054  $recArr['prevsection_last'] = end($sectionRec_temp);
1055  }
1056  }
1057  ‪$nextActive = 1;
1058  }
1059  $lastKey = $k_b;
1060  }
1061  }
1062  if ($this->conf['special.']['items.']['prevnextToSection'] ?? false) {
1063  if (!is_array($recArr['prev'] ?? false) && is_array($recArr['prevsection_last'] ?? false)) {
1064  $recArr['prev'] = $recArr['prevsection_last'];
1065  }
1066  if (!is_array($recArr['next'] ?? false) && is_array($recArr['nextsection'] ?? false)) {
1067  $recArr['next'] = $recArr['nextsection'];
1068  }
1069  }
1070  $items = explode('|', $this->conf['special.']['items']);
1071  $c = 0;
1072  foreach ($items as $k_b => $v_b) {
1073  $v_b = strtolower(trim($v_b));
1074  if ((int)($this->conf['special.'][$v_b . '.']['uid'] ?? false)) {
1075  $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1076  }
1077  if (is_array($recArr[$v_b] ?? false)) {
1078  $menuItems[$c] = $recArr[$v_b];
1079  if ($this->conf['special.'][$v_b . '.']['target'] ?? false) {
1080  $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1081  }
1082  foreach ((array)($this->conf['special.'][$v_b . '.']['fields.'] ?? []) as $fk => $val) {
1083  $menuItems[$c][$fk] = $val;
1084  }
1085  $c++;
1086  }
1087  }
1088  }
1089  return $menuItems;
1090  }
1091 
1103  public function ‪filterMenuPages(&$data, $banUidArray, $isSpacerPage)
1104  {
1105  $includePage = true;
1106  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] ?? [] as $className) {
1107  $hookObject = GeneralUtility::makeInstance($className);
1108  if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1109  throw new \UnexpectedValueException($className . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1110  }
1111  $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $isSpacerPage, $this);
1112  }
1113  if (!$includePage) {
1114  return false;
1115  }
1116  if ($data['_SAFE'] ?? false) {
1117  return true;
1118  }
1119  // If the spacer-function is not enabled, spacers will not enter the $menuArr
1120  if (!($this->mconf['SPC'] ?? false) && $isSpacerPage) {
1121  return false;
1122  }
1123  // Page may not be a 'Backend User Section' or any other excluded doktype
1124  if (in_array((int)($data['doktype'] ?? 0), $this->excludedDoktypes, true)) {
1125  return false;
1126  }
1127  $languageId = $this->‪getCurrentLanguageAspect()->getId();
1128  // PageID should not be banned (check for default language pages as well)
1129  if (($data['_PAGES_OVERLAY_UID'] ?? 0) > 0 && in_array((int)($data['_PAGES_OVERLAY_UID'] ?? 0), $banUidArray, true)) {
1130  return false;
1131  }
1132  if (in_array((int)($data['uid'] ?? 0), $banUidArray, true)) {
1133  return false;
1134  }
1135  // If the page is hide in menu, but the menu does not include them do not show the page
1136  if (($data['nav_hide'] ?? false) && !($this->conf['includeNotInMenu'] ?? false)) {
1137  return false;
1138  }
1139  // Checking if a page should be shown in the menu depending on whether a translation exists or if the default language is disabled
1140  if (!$this->sys_page->isPageSuitableForLanguage($data, $this->getCurrentLanguageAspect())) {
1141  return false;
1142  }
1143  // Checking if the link should point to the default language so links to non-accessible pages will not happen
1144  if ($languageId > 0 && !empty($this->conf['protectLvar'])) {
1145  $pageTranslationVisibility = new PageTranslationVisibility((int)($data['l18n_cfg'] ?? 0));
1146  if ($this->conf['protectLvar'] === 'all' || $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()) {
1147  $olRec = $this->sys_page->getPageOverlay($data['uid'], $languageId);
1148  if (empty($olRec)) {
1149  // If no page translation record then page can NOT be accessed in
1150  // the language pointed to, therefore we protect the link by linking to the default language
1151  $data['_PAGES_OVERLAY_REQUESTEDLANGUAGE'] = '0';
1152  }
1153  }
1154  }
1155  return true;
1156  }
1157 
1171  protected function ‪processItemStates($splitCount)
1172  {
1173  // Prepare normal settings
1174  if (!is_array($this->mconf['NO.'] ?? null) && $this->mconf['NO']) {
1175  // Setting a blank array if NO=1 and there are no properties.
1176  $this->mconf['NO.'] = [];
1177  }
1178  $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1179  $NOconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['NO.'], $splitCount);
1180 
1181  // Prepare custom states settings, overriding normal settings
1182  foreach (self::customItemStates as $state) {
1183  if (empty($this->mconf[$state])) {
1184  continue;
1185  }
1186  $customConfiguration = null;
1187  foreach ($NOconf as $key => $val) {
1188  if ($this->‪isItemState($state, $key)) {
1189  // if this is the first element of type $state, we must generate the custom configuration.
1190  if ($customConfiguration === null) {
1191  $customConfiguration = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf[$state . '.'], $splitCount);
1192  }
1193  // Substitute normal with the custom (e.g. IFSUB)
1194  if (isset($customConfiguration[$key])) {
1195  $NOconf[$key] = $customConfiguration[$key];
1196  }
1197  }
1198  }
1199  }
1200 
1201  return $NOconf;
1202  }
1203 
1213  protected function ‪link($key, $altTarget, $typeOverride)
1214  {
1215  $attrs = [];
1216  $runtimeCache = $this->‪getRuntimeCache();
1217  $MP_var = $this->‪getMPvar($key);
1218  $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . $MP_var . ((string)($this->mconf['showAccessRestrictedPages'] ?? '_')) . json_encode($this->menuArr[$key]));
1219  $runtimeCachedLink = $runtimeCache->get($cacheId);
1220  if ($runtimeCachedLink !== false) {
1221  return $runtimeCachedLink;
1222  }
1223 
1224  $tsfe = $this->‪getTypoScriptFrontendController();
1225 
1226  $SAVED_link_to_restricted_pages = '';
1227  $SAVED_link_to_restricted_pages_additional_params = '';
1228  // links to a specific page
1229  if ($this->mconf['showAccessRestrictedPages'] ?? false) {
1230  $SAVED_link_to_restricted_pages = $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] ?? false;
1231  $SAVED_link_to_restricted_pages_additional_params = $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams'] ?? null;
1232  $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] = $this->mconf['showAccessRestrictedPages'];
1233  $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams'] = $this->mconf['showAccessRestrictedPages.']['addParams'] ?? '';
1234  }
1235  // If a user script returned the value overrideId in the menu array we use that as page id
1236  if (($this->mconf['overrideId'] ?? false) || ($this->menuArr[$key]['overrideId'] ?? false)) {
1237  $overrideId = (int)($this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId']);
1238  $overrideId = $overrideId > 0 ? $overrideId : null;
1239  // Clear MP parameters since ID was changed.
1240  $MP_params = '';
1241  } else {
1242  $overrideId = null;
1243  // Mount points:
1244  $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1245  }
1246  // Setting main target
1247  $mainTarget = $altTarget ?: (string)$this->parent_cObj->stdWrapValue('target', $this->mconf ?? []);
1248  // Creating link:
1249  $addParams = ($this->mconf['addParams'] ?? '') . $MP_params;
1250  if (($this->mconf['collapse'] ?? false) && $this->‪isActive($this->menuArr[$key] ?? [], $this->‪getMPvar($key))) {
1251  $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1252  $LD = $this->‪menuTypoLink($thePage, $mainTarget, $addParams, $typeOverride, $overrideId);
1253  } else {
1254  $addParams .= ($this->I['val']['additionalParams'] ?? '');
1255  $LD = $this->‪menuTypoLink($this->menuArr[$key], $mainTarget, $addParams, $typeOverride, $overrideId);
1256  }
1257  // Overriding URL / Target if set to do so:
1258  if ($this->menuArr[$key]['_OVERRIDE_HREF'] ?? false) {
1259  $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1260  if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1261  $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1262  }
1263  }
1264  // opens URL in new window
1265  // @deprecated will be removed in TYPO3 v12.0.
1266  if ($this->mconf['JSWindow'] ?? false) {
1267  trigger_error('Calling HMENU with option JSwindow will stop working in TYPO3 v12.0. Use a external JavaScript file with proper event listeners to open a custom window.', E_USER_DEPRECATED);
1268  ‪$conf = $this->mconf['JSWindow.'];
1269  $url = $LD['totalURL'];
1270  $LD['totalURL'] = '#';
1271  $attrs['data-window-url'] = $tsfe->baseUrlWrap($url);
1272  $attrs['data-window-target'] = ‪$conf['newWindow'] ? md5($url) : 'theNewPage';
1273  if (!empty(‪$conf['params'])) {
1274  $attrs['data-window-features'] = ‪$conf['params'];
1275  }
1277  }
1278  // look for type and popup
1279  // following settings are valid in field target:
1280  // 230 will add type=230 to the link
1281  // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1282  // 230 _blank will add type=230 to the link and open with target "_blank"
1283  // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1284  $matches = [];
1285  $targetIsType = ($LD['target'] ?? false) && ‪MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1286  if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', ($LD['target'] ?? ''), $matches) || $targetIsType) {
1287  // has type?
1288  if ((int)($matches[1] ?? 0) || $targetIsType) {
1289  $LD['totalURL'] .= (!str_contains($LD['totalURL'], '?') ? '?' : '&') . 'type=' . ($targetIsType ?: (int)$matches[1]);
1290  $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1291  }
1292  // Open in popup window?
1293  // @deprecated will be removed in TYPO3 v12.0.
1294  if (($matches[3] ?? false) && ($matches[4] ?? false)) {
1295  trigger_error('Calling HMENU with a special target to open a link in a window will be removed in TYPO3 v12.0. Use a external JavaScript file with proper event listeners to open a custom window.', E_USER_DEPRECATED);
1296  $attrs['data-window-url'] = $tsfe->baseUrlWrap($LD['totalURL']);
1297  $attrs['data-window-target'] = $LD['target'] ?? 'FEopenLink';
1298  $attrs['data-window-features'] = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1299  $LD['target'] = '';
1301  }
1302  }
1303  // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1304  // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1305  // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1306  $attrs['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1307  $attrs['TARGET'] = $LD['target'] ?? '';
1308  $runtimeCache->set($cacheId, $attrs);
1309 
1310  // End showAccessRestrictedPages
1311  if ($this->mconf['showAccessRestrictedPages'] ?? false) {
1312  $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] = $SAVED_link_to_restricted_pages;
1313  $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams'] = $SAVED_link_to_restricted_pages_additional_params;
1314  }
1315 
1316  return $attrs;
1317  }
1318 
1328  protected function ‪determineOriginalShortcutPage(array $page)
1329  {
1330  // Check if modification is required
1331  if (
1332  $this->‪getCurrentLanguageAspect()->getId() > 0
1333  && empty($page['shortcut'])
1334  && !empty($page['uid'])
1335  && !empty($page['_PAGES_OVERLAY'])
1336  && !empty($page['_PAGES_OVERLAY_UID'])
1337  ) {
1338  // Using raw record since the record was overlaid and is correct already:
1339  $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1340 
1341  if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1342  $page['shortcut'] = $originalPage['shortcut'];
1343  }
1344  }
1345 
1346  return $page;
1347  }
1348 
1356  protected function ‪subMenu(int $uid, string $objSuffix)
1357  {
1358  // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1359  $altArray = '';
1360  if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU'] ?? null) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1361  $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1362  }
1363  // Make submenu if the page is the next active
1364  $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix] ?? '';
1365  // stdWrap for expAll
1366  $this->mconf['expAll'] = $this->parent_cObj->stdWrapValue('expAll', $this->mconf ?? []);
1367  if (($this->mconf['expAll'] || $this->‪isNext($uid, $this->‪getMPvar($this->I['key'])) || is_array($altArray)) && !($this->mconf['sectionIndex'] ?? false)) {
1368  try {
1369  $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1371  $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1372  $submenu->entryLevel = $this->entryLevel + 1;
1373  $submenu->rL_uidRegister = ‪$this->rL_uidRegister;
1374  $submenu->MP_array = ‪$this->MP_array;
1375  if ($this->menuArr[$this->I['key']]['_MP_PARAM'] ?? false) {
1376  $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1377  }
1378  // Especially scripts that build the submenu needs the parent data
1379  $submenu->parent_cObj = ‪$this->parent_cObj;
1380  $submenu->setParentMenu($this->menuArr, $this->I['key']);
1381  // Setting alternativeMenuTempArray (will be effective only if an array and not empty)
1382  if (is_array($altArray) && !empty($altArray)) {
1383  $submenu->alternativeMenuTempArray = $altArray;
1384  }
1385  if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1386  $submenu->makeMenu();
1387  // Memorize the current menu item count
1388  $tsfe = $this->‪getTypoScriptFrontendController();
1389  $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1390  // Reset the menu item count for the submenu
1391  $tsfe->register['count_MENUOBJ'] = 0;
1392  $content = $submenu->writeMenu();
1393  // Restore the item count now that the submenu has been handled
1394  $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1395  $tsfe->register['count_menuItems'] = count($this->menuArr);
1396  return $content;
1397  }
1398  } catch (‪NoSuchMenuTypeException $e) {
1399  }
1400  }
1401  return '';
1402  }
1403 
1412  protected function ‪isNext($uid, $MPvar)
1413  {
1414  // Check for always active PIDs:
1415  if (in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1416  return true;
1417  }
1418  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1419  if ($uid && $testUid == $this->nextActive) {
1420  return true;
1421  }
1422  return false;
1423  }
1424 
1432  protected function ‪isActive(array $page, $MPvar)
1433  {
1434  // Check for always active PIDs
1435  $uid = (int)($page['uid'] ?? 0);
1436  if (in_array($uid, $this->alwaysActivePIDlist, true)) {
1437  return true;
1438  }
1439  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1440  if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1441  return true;
1442  }
1443  try {
1444  $page = $this->sys_page->resolveShortcutPage($page);
1445  $shortcutPage = (int)($page['_SHORTCUT_ORIGINAL_PAGE_UID'] ?? 0);
1446  if ($shortcutPage) {
1447  if (in_array($shortcutPage, $this->alwaysActivePIDlist, true)) {
1448  return true;
1449  }
1450  $testUid = $shortcutPage . ($MPvar ? ':' . $MPvar : '');
1451  if (in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1452  return true;
1453  }
1454  }
1455  } catch (\‪Exception $e) {
1456  // Shortcut could not be resolved
1457  return false;
1458  }
1459  return false;
1460  }
1461 
1469  protected function ‪isCurrent(array $page, $MPvar)
1470  {
1471  $testUid = ($page['uid'] ?? 0) . ($MPvar ? ':' . $MPvar : '');
1472  if (($page['uid'] ?? 0) && end($this->rL_uidRegister) === 'ITEM:' . $testUid) {
1473  return true;
1474  }
1475  try {
1476  $page = $this->sys_page->resolveShortcutPage($page);
1477  $shortcutPage = (int)($page['_SHORTCUT_ORIGINAL_PAGE_UID'] ?? 0);
1478  if ($shortcutPage) {
1479  $testUid = $shortcutPage . ($MPvar ? ':' . $MPvar : '');
1480  if (end($this->rL_uidRegister) === 'ITEM:' . $testUid) {
1481  return true;
1482  }
1483  }
1484  } catch (\‪Exception $e) {
1485  // Shortcut could not be resolved
1486  return false;
1487  }
1488  return false;
1489  }
1490 
1498  protected function ‪isSubMenu($uid)
1499  {
1500  $cacheId = 'menucontentobject-is-submenu-decision-' . $uid . '-' . (int)($this->conf['includeNotInMenu'] ?? 0);
1501  $runtimeCache = $this->‪getRuntimeCache();
1502  $cachedDecision = $runtimeCache->get($cacheId);
1503  if (isset($cachedDecision['result'])) {
1504  return $cachedDecision['result'];
1505  }
1506  // Looking for a mount-pid for this UID since if that
1507  // exists we should look for a subpages THERE and not in the input $uid;
1508  $mount_info = $this->sys_page->getMountPointInfo($uid);
1509  if (is_array($mount_info)) {
1510  $uid = $mount_info['mount_pid'];
1511  }
1512  $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1513  $hasSubPages = false;
1514  $bannedUids = $this->‪getBannedUids();
1515  $languageId = $this->‪getCurrentLanguageAspect()->getId();
1516  foreach ($recs as $theRec) {
1517  // no valid subpage if the document type is excluded from the menu
1518  if (in_array((int)($theRec['doktype'] ?? 0), $this->excludedDoktypes, true)) {
1519  continue;
1520  }
1521  // No valid subpage if the page is hidden inside menus and
1522  // it wasn't forced to show such entries
1523  if (isset($theRec['nav_hide']) && $theRec['nav_hide']
1524  && (!isset($this->conf['includeNotInMenu']) || !$this->conf['includeNotInMenu'])
1525  ) {
1526  continue;
1527  }
1528  // No valid subpage if the default language should be shown and the page settings
1529  // are excluding the visibility of the default language
1530  $pageTranslationVisibility = new PageTranslationVisibility((int)($theRec['l18n_cfg'] ?? 0));
1531  if (!$languageId && $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage()) {
1532  continue;
1533  }
1534  // No valid subpage if the alternative language should be shown and the page settings
1535  // are requiring a valid overlay but it doesn't exists
1536  if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() && $languageId > 0 && !($theRec['_PAGES_OVERLAY'] ?? false)) {
1537  continue;
1538  }
1539  // No valid subpage if the subpage is banned by excludeUidList (check for default language pages as well)
1540  if (($theRec['_PAGES_OVERLAY_UID'] ?? 0) > 0 && in_array((int)($theRec['_PAGES_OVERLAY_UID'] ?? 0), $bannedUids, true)) {
1541  continue;
1542  }
1543  if (in_array((int)($theRec['uid'] ?? 0), $bannedUids, true)) {
1544  continue;
1545  }
1546  $hasSubPages = true;
1547  break;
1548  }
1549  $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1550  return $hasSubPages;
1551  }
1552 
1561  protected function ‪isItemState($kind, $key)
1562  {
1563  $natVal = false;
1564  // If any value is set for ITEM_STATE the normal evaluation is discarded
1565  if ($this->menuArr[$key]['ITEM_STATE'] ?? false) {
1566  if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1567  $natVal = true;
1568  }
1569  } else {
1570  switch ($kind) {
1571  case 'SPC':
1572  $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1573  break;
1574  case 'IFSUB':
1575  $natVal = $this->‪isSubMenu($this->menuArr[$key]['uid'] ?? 0);
1576  break;
1577  case 'ACT':
1578  $natVal = $this->‪isActive(($this->menuArr[$key] ?? []), $this->‪getMPvar($key));
1579  break;
1580  case 'ACTIFSUB':
1581  $natVal = $this->‪isActive(($this->menuArr[$key] ?? []), $this->‪getMPvar($key)) && $this->‪isSubMenu($this->menuArr[$key]['uid']);
1582  break;
1583  case 'CUR':
1584  $natVal = $this->‪isCurrent(($this->menuArr[$key] ?? []), $this->‪getMPvar($key));
1585  break;
1586  case 'CURIFSUB':
1587  $natVal = $this->‪isCurrent(($this->menuArr[$key] ?? []), $this->‪getMPvar($key)) && $this->‪isSubMenu($this->menuArr[$key]['uid']);
1588  break;
1589  case 'USR':
1590  $natVal = (bool)$this->menuArr[$key]['fe_group'];
1591  break;
1592  }
1593  }
1594  return $natVal;
1595  }
1596 
1603  protected function ‪accessKey($title)
1604  {
1605  $tsfe = $this->‪getTypoScriptFrontendController();
1606  // The global array ACCESSKEY is used to globally control if letters are already used!!
1607  ‪$result = [];
1608  $title = trim(strip_tags($title));
1609  $titleLen = strlen($title);
1610  for ($a = 0; $a < $titleLen; $a++) {
1611  $key = strtoupper(substr($title, $a, 1));
1612  if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
1613  $tsfe->accessKey[$key] = true;
1614  ‪$result['code'] = ' accesskey="' . $key . '"';
1615  ‪$result['alt'] = ' (ALT+' . $key . ')';
1616  ‪$result['key'] = $key;
1617  break;
1618  }
1619  }
1620  return ‪$result;
1621  }
1622 
1631  protected function ‪userProcess($mConfKey, $passVar)
1632  {
1633  if ($this->mconf[$mConfKey]) {
1634  $funcConf = (array)($this->mconf[$mConfKey . '.'] ?? []);
1635  $funcConf['parentObj'] = $this;
1636  $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
1637  }
1638  return $passVar;
1639  }
1640 
1644  protected function ‪setATagParts()
1645  {
1646  $params = trim($this->I['val']['ATagParams']) . ($this->I['accessKey']['code'] ?? '');
1647  $params = $params !== '' ? ' ' . $params : '';
1648  $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], true) . $params . '>';
1649  $this->I['A2'] = '</a>';
1650  }
1651 
1659  protected function ‪getPageTitle($title, $nav_title)
1660  {
1661  return trim($nav_title) !== '' ? $nav_title : $title;
1662  }
1663 
1671  protected function ‪getMPvar($key)
1672  {
1673  if (‪$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
1674  $localMP_array = ‪$this->MP_array;
1675  // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
1676  if ($this->menuArr[$key]['_MP_PARAM'] ?? false) {
1677  $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
1678  }
1679  return !empty($localMP_array) ? implode(',', $localMP_array) : '';
1680  }
1681  return '';
1682  }
1683 
1689  protected function ‪getDoktypeExcludeWhere()
1690  {
1691  return !empty($this->excludedDoktypes) ? ' AND pages.doktype NOT IN (' . implode(',', $this->excludedDoktypes) . ')' : '';
1692  }
1693 
1699  protected function ‪getBannedUids()
1700  {
1701  $excludeUidList = (string)$this->parent_cObj->stdWrapValue('excludeUidList', $this->conf ?? []);
1702  if (!trim($excludeUidList)) {
1703  return [];
1704  }
1705 
1706  $banUidList = str_replace('current', (string)($this->‪getTypoScriptFrontendController()->page['uid'] ?? ''), $excludeUidList);
1707  return ‪GeneralUtility::intExplode(',', $banUidList);
1708  }
1709 
1720  protected function ‪menuTypoLink($page, $oTarget, $addParams, $typeOverride, ?int $overridePageId = null)
1721  {
1722  ‪$conf = [
1723  'parameter' => $overridePageId ?? $page['uid'] ?? 0,
1724  ];
1725  if (‪MathUtility::canBeInterpretedAsInteger($typeOverride)) {
1726  ‪$conf['parameter'] .= ',' . (int)$typeOverride;
1727  }
1728  if ($addParams) {
1729  ‪$conf['additionalParams'] = $addParams;
1730  }
1731  // Used only for special=language
1732  if ($page['_ADD_GETVARS'] ?? false) {
1733  ‪$conf['addQueryString'] = 1;
1734  ‪$conf['addQueryString.'] = $this->conf['addQueryString.'] ?? [];
1735  }
1736 
1737  // Ensure that the typolink gets an info which language was actually requested. The $page record could be the record
1738  // from page translation language=1 as fallback but page translation language=2 was requested. Search for
1739  // "_PAGES_OVERLAY_REQUESTEDLANGUAGE" for more details
1740  if (isset($page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'])) {
1741  ‪$conf['language'] = $page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'];
1742  }
1743  if ($oTarget) {
1744  ‪$conf['target'] = $oTarget;
1745  }
1746  if ($page['sectionIndex_uid'] ?? false) {
1747  ‪$conf['section'] = $page['sectionIndex_uid'];
1748  }
1749  $this->parent_cObj->typoLink('|', ‪$conf);
1750  $LD = $this->parent_cObj->lastTypoLinkLD;
1751  $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
1752  return $LD;
1753  }
1754 
1766  protected function ‪sectionIndex($altSortField, $pid = null)
1767  {
1768  $pid = (int)($pid ?: $this->id);
1769  $basePageRow = $this->sys_page->getPage($pid);
1770  if (!is_array($basePageRow)) {
1771  return [];
1772  }
1773  $useColPos = (int)$this->parent_cObj->stdWrapValue('useColPos', $this->mconf['sectionIndex.'] ?? [], 0);
1774  $selectSetup = [
1775  'pidInList' => $pid,
1776  'orderBy' => $altSortField,
1777  'languageField' => 'sys_language_uid',
1778  'where' => '',
1779  ];
1780 
1781  if ($useColPos >= 0) {
1782  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1783  ->getConnectionForTable('tt_content')
1784  ->getExpressionBuilder();
1785  $selectSetup['where'] = $expressionBuilder->eq('colPos', $useColPos);
1786  }
1787 
1788  if ($basePageRow['content_from_pid'] ?? false) {
1789  // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
1790  // the referenced page
1791  $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
1792  }
1793  $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
1794  if (!$statement) {
1795  $message = 'SectionIndex: Query to fetch the content elements failed!';
1796  throw new \UnexpectedValueException($message, 1337334849);
1797  }
1798  ‪$result = [];
1799  while ($row = $statement->fetchAssociative()) {
1800  $this->sys_page->versionOL('tt_content', $row);
1801  if ($this->‪getCurrentLanguageAspect()->doOverlays() && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
1802  $languageAspect = new LanguageAspect($basePageRow['_PAGES_OVERLAY_LANGUAGE'], $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $this->‪getCurrentLanguageAspect()->getOverlayType());
1803  $row = $this->sys_page->getRecordOverlay(
1804  'tt_content',
1805  $row,
1806  $languageAspect
1807  );
1808  }
1809  if (is_array($row)) {
1810  $sectionIndexType = $this->mconf['sectionIndex.']['type'] ?? '';
1811  if ($sectionIndexType !== 'all') {
1812  $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
1813  $doHeaderCheck = $sectionIndexType === 'header';
1814  $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
1815  if (!$doIncludeInSectionIndex || ($doHeaderCheck && !$isValidHeader)) {
1816  continue;
1817  }
1818  }
1819  $uid = $row['uid'] ?? null;
1820  ‪$result[$uid] = $basePageRow;
1821  ‪$result[$uid]['title'] = $row['header'];
1822  ‪$result[$uid]['nav_title'] = $row['header'];
1823  // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
1824  ‪$result[$uid]['nav_hide'] = 0;
1825  ‪$result[$uid]['subtitle'] = $row['subheader'] ?? '';
1826  ‪$result[$uid]['starttime'] = $row['starttime'] ?? '';
1827  ‪$result[$uid]['endtime'] = $row['endtime'] ?? '';
1828  ‪$result[$uid]['fe_group'] = $row['fe_group'] ?? '';
1829  ‪$result[$uid]['media'] = $row['media'] ?? '';
1830  ‪$result[$uid]['header_layout'] = $row['header_layout'] ?? '';
1831  ‪$result[$uid]['bodytext'] = $row['bodytext'] ?? '';
1832  ‪$result[$uid]['image'] = $row['image'] ?? '';
1833  ‪$result[$uid]['sectionIndex_uid'] = $uid;
1834  }
1835  }
1836 
1837  return ‪$result;
1838  }
1839 
1845  public function ‪getSysPage()
1846  {
1847  return ‪$this->sys_page;
1848  }
1849 
1855  public function ‪getParentContentObject()
1856  {
1857  return ‪$this->parent_cObj;
1858  }
1859 
1861  {
1862  $frontendController = $this->parent_cObj->getTypoScriptFrontendController();
1863  if (!$frontendController instanceof ‪TypoScriptFrontendController) {
1864  throw new ‪ContentRenderingException('TypoScriptFrontendController is not available.', 1655725105);
1865  }
1866 
1867  return $frontendController;
1868  }
1870  protected function ‪getCurrentLanguageAspect(): ‪LanguageAspect
1871  {
1872  return GeneralUtility::makeInstance(Context::class)->getAspect('language');
1873  }
1874 
1878  protected function ‪getTimeTracker()
1879  {
1880  return GeneralUtility::makeInstance(TimeTracker::class);
1881  }
1882 
1886  protected function ‪getCache()
1887  {
1888  return GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
1889  }
1890 
1894  protected function ‪getRuntimeCache()
1895  {
1896  return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
1897  }
1898 
1904  protected function ‪getCurrentSite(): Site
1905  {
1906  return $this->‪getTypoScriptFrontendController()->getSite();
1907  }
1917  public function ‪setParentMenu(array ‪$menuArr, $menuItemKey)
1918  {
1919  // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
1920  if (is_array(‪$menuArr)
1921  && (is_int($menuItemKey) && $menuItemKey >= 0 && isset(‪$menuArr[$menuItemKey]))
1922  ) {
1923  $this->parentMenuArr = ‪$menuArr;
1924  $this->parentMenuArrItemKey = $menuItemKey;
1925  }
1926  }
1927 
1933  protected function ‪hasParentMenuArr()
1934  {
1935  return
1936  $this->menuNumber > 1
1937  && is_array($this->parentMenuArr)
1938  && !empty($this->parentMenuArr)
1939  ;
1940  }
1941 
1945  protected function ‪hasParentMenuItemKey()
1946  {
1947  return $this->parentMenuArrItemKey !== null;
1948  }
1949 
1953  protected function ‪hasParentMenuItem()
1954  {
1955  return
1956  $this->‪hasParentMenuArr()
1957  && $this->‪hasParentMenuItemKey()
1958  && isset($this->‪getParentMenuArr()[$this->parentMenuArrItemKey])
1959  ;
1960  }
1961 
1967  public function ‪getParentMenuArr()
1968  {
1969  return $this->‪hasParentMenuArr() ? $this->parentMenuArr : [];
1970  }
1971 
1977  public function ‪getParentMenuItem()
1978  {
1979  // check if we have a parentMenuItem and if it is an array
1980  if ($this->‪hasParentMenuItem()
1981  && is_array($this->‪getParentMenuArr()[$this->parentMenuArrItemKey])
1982  ) {
1984  }
1985 
1986  return null;
1987  }
1988 
1993  private function ‪getMode(string $mode = ''): string
1994  {
1995  switch ($mode) {
1996  case 'starttime':
1997  $sortField = 'starttime';
1998  break;
1999  case 'lastUpdated':
2000  case 'manual':
2001  $sortField = 'lastUpdated';
2002  break;
2003  case 'tstamp':
2004  $sortField = 'tstamp';
2005  break;
2006  case 'crdate':
2007  $sortField = 'crdate';
2008  break;
2009  default:
2010  $sortField = 'SYS_LASTCHANGED';
2011  }
2012 
2013  return $sortField;
2014  }
2015 }
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForListMenu
‪array prepareMenuItemsForListMenu($specialValue)
Definition: AbstractMenuContentObject.php:668
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getBannedUids
‪array getBannedUids()
Definition: AbstractMenuContentObject.php:1674
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getParentMenuItem
‪array null getParentMenuItem()
Definition: AbstractMenuContentObject.php:1952
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getTimeTracker
‪TimeTracker getTimeTracker()
Definition: AbstractMenuContentObject.php:1853
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:999
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$MP_array
‪string[] $MP_array
Definition: AbstractMenuContentObject.php:82
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMresult
‪string $WMresult
Definition: AbstractMenuContentObject.php:144
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$parentMenuArrItemKey
‪int null $parentMenuArrItemKey
Definition: AbstractMenuContentObject.php:170
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\hasParentMenuArr
‪bool hasParentMenuArr()
Definition: AbstractMenuContentObject.php:1908
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getPageTitle
‪string getPageTitle($title, $nav_title)
Definition: AbstractMenuContentObject.php:1634
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isNext
‪bool isNext($uid, $MPvar)
Definition: AbstractMenuContentObject.php:1387
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\writeMenu
‪string writeMenu()
Definition: AbstractMenuContentObject.php:445
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\menuTypoLink
‪array menuTypoLink($page, $oTarget, $addParams, $typeOverride, ?int $overridePageId=null)
Definition: AbstractMenuContentObject.php:1695
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException
Definition: NoSuchMenuTypeException.php:23
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForRootlineMenu
‪array prepareMenuItemsForRootlineMenu()
Definition: AbstractMenuContentObject.php:908
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$parentMenuArr
‪array $parentMenuArr
Definition: AbstractMenuContentObject.php:174
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\hasParentMenuItem
‪hasParentMenuItem()
Definition: AbstractMenuContentObject.php:1928
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$nextActive
‪string $nextActive
Definition: AbstractMenuContentObject.php:115
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$conf
‪array $conf
Definition: AbstractMenuContentObject.php:88
‪TYPO3\CMS\Core\TimeTracker\TimeTracker\setTSlogMessage
‪setTSlogMessage($content, $logLevel=LogLevel::INFO)
Definition: TimeTracker.php:226
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItems
‪array prepareMenuItems()
Definition: AbstractMenuContentObject.php:473
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$alternativeMenuTempArray
‪array $alternativeMenuTempArray
Definition: AbstractMenuContentObject.php:164
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\sectionIndex
‪array sectionIndex($altSortField, $pid=null)
Definition: AbstractMenuContentObject.php:1741
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForUpdatedMenu
‪array prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:724
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForLanguageMenu
‪array prepareMenuItemsForLanguageMenu($specialValue)
Definition: AbstractMenuContentObject.php:563
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getRuntimeCache
‪TYPO3 CMS Core Cache Frontend FrontendInterface getRuntimeCache()
Definition: AbstractMenuContentObject.php:1869
‪TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility
Definition: PageTranslationVisibility.php:30
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\hasParentMenuItemKey
‪hasParentMenuItemKey()
Definition: AbstractMenuContentObject.php:1920
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject
Definition: AbstractMenuContentObject.php:48
‪TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException
Definition: ContentRenderingException.php:24
‪TYPO3\CMS\Core\Database\Connection\PARAM_STR
‪const PARAM_STR
Definition: Connection.php:54
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:53
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForUserSpecificMenu
‪array prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:547
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\filterMenuPages
‪bool filterMenuPages(&$data, $banUidArray, $isSpacerPage)
Definition: AbstractMenuContentObject.php:1078
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getCurrentSite
‪Site getCurrentSite()
Definition: AbstractMenuContentObject.php:1879
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$result
‪array $result
Definition: AbstractMenuContentObject.php:129
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\userProcess
‪mixed userProcess($mConfKey, $passVar)
Definition: AbstractMenuContentObject.php:1606
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\subMenu
‪string subMenu(int $uid, string $objSuffix)
Definition: AbstractMenuContentObject.php:1331
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getDoktypeExcludeWhere
‪string getDoktypeExcludeWhere()
Definition: AbstractMenuContentObject.php:1664
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$sys_page
‪PageRepository $sys_page
Definition: AbstractMenuContentObject.php:102
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$I
‪mixed[] $I
Definition: AbstractMenuContentObject.php:140
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$menuArr
‪array[] $menuArr
Definition: AbstractMenuContentObject.php:121
‪TYPO3\CMS\Frontend\Exception
Definition: Exception.php:23
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\generate
‪generate()
Definition: AbstractMenuContentObject.php:440
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isCurrent
‪bool isCurrent(array $page, $MPvar)
Definition: AbstractMenuContentObject.php:1444
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$parent_cObj
‪ContentObjectRenderer $parent_cObj
Definition: AbstractMenuContentObject.php:76
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SPACER
‪const DOKTYPE_SPACER
Definition: PageRepository.php:115
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMsubmenuObjSuffixes
‪array[] $WMsubmenuObjSuffixes
Definition: AbstractMenuContentObject.php:152
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getTypoScriptFrontendController
‪getTypoScriptFrontendController()
Definition: AbstractMenuContentObject.php:1835
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_BE_USER_SECTION
‪const DOKTYPE_BE_USER_SECTION
Definition: PageRepository.php:113
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$id
‪int $id
Definition: AbstractMenuContentObject.php:108
‪TYPO3\CMS\Core\Page\DefaultJavaScriptAssetTrait
Definition: DefaultJavaScriptAssetTrait.php:30
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuFilterPagesHookInterface
Definition: AbstractMenuFilterPagesHookInterface.php:22
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SYSFOLDER
‪const DOKTYPE_SYSFOLDER
Definition: PageRepository.php:116
‪TYPO3\CMS\Core\Context\LanguageAspect
Definition: LanguageAspect.php:57
‪TYPO3\CMS\Frontend\ContentObject\Menu
Definition: AbstractMenuContentObject.php:16
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForBrowseMenu
‪array prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
Definition: AbstractMenuContentObject.php:962
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\makeMenu
‪makeMenu()
Definition: AbstractMenuContentObject.php:343
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\customItemStates
‪const customItemStates
Definition: AbstractMenuContentObject.php:176
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$entryLevel
‪int $entryLevel
Definition: AbstractMenuContentObject.php:60
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\setParentMenu
‪setParentMenu(array $menuArr, $menuItemKey)
Definition: AbstractMenuContentObject.php:1892
‪TYPO3\CMS\Core\TypoScript\TypoScriptService
Definition: TypoScriptService.php:25
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\start
‪bool start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix='', ?ServerRequestInterface $request=null)
Definition: AbstractMenuContentObject.php:205
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getCurrentLanguageAspect
‪getCurrentLanguageAspect()
Definition: AbstractMenuContentObject.php:1845
‪TYPO3\CMS\Core\Resource\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$tmpl
‪TemplateService $tmpl
Definition: AbstractMenuContentObject.php:98
‪TYPO3\CMS\Core\TypoScript\TemplateService
Definition: TemplateService.php:46
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\removeInaccessiblePages
‪array removeInaccessiblePages(array $pages)
Definition: AbstractMenuContentObject.php:456
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isActive
‪bool isActive(array $page, $MPvar)
Definition: AbstractMenuContentObject.php:1407
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMmenuItems
‪int $WMmenuItems
Definition: AbstractMenuContentObject.php:148
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$rL_uidRegister
‪array $rL_uidRegister
Definition: AbstractMenuContentObject.php:136
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
Definition: TypoScriptFrontendController.php:104
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$menuNumber
‪int $menuNumber
Definition: AbstractMenuContentObject.php:54
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForKeywordsMenu
‪array prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:791
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:927
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\determineOriginalShortcutPage
‪array determineOriginalShortcutPage(array $page)
Definition: AbstractMenuContentObject.php:1303
‪TYPO3\CMS\Core\Page\DefaultJavaScriptAssetTrait\addDefaultFrontendJavaScript
‪addDefaultFrontendJavaScript()
Definition: DefaultJavaScriptAssetTrait.php:32
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getParentContentObject
‪ContentObjectRenderer getParentContentObject()
Definition: AbstractMenuContentObject.php:1830
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$hash
‪string $hash
Definition: AbstractMenuContentObject.php:125
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\processItemStates
‪array processItemStates($splitCount)
Definition: AbstractMenuContentObject.php:1146
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$excludedDoktypes
‪int[] $excludedDoktypes
Definition: AbstractMenuContentObject.php:66
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:53
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$mconf
‪array $mconf
Definition: AbstractMenuContentObject.php:94
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getSysPage
‪PageRepository getSysPage()
Definition: AbstractMenuContentObject.php:1820
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$alwaysActivePIDlist
‪int[] $alwaysActivePIDlist
Definition: AbstractMenuContentObject.php:70
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\setATagParts
‪setATagParts()
Definition: AbstractMenuContentObject.php:1619
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getParentMenuArr
‪array getParentMenuArr()
Definition: AbstractMenuContentObject.php:1942
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:31
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getMode
‪string getMode(string $mode='')
Definition: AbstractMenuContentObject.php:1968
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\accessKey
‪array accessKey($title)
Definition: AbstractMenuContentObject.php:1578
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$request
‪ServerRequestInterface $request
Definition: AbstractMenuContentObject.php:158
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\prepareMenuItemsForDirectoryMenu
‪array prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
Definition: AbstractMenuContentObject.php:626
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getMPvar
‪string getMPvar($key)
Definition: AbstractMenuContentObject.php:1646
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\$WMcObj
‪ContentObjectRenderer $WMcObj
Definition: AbstractMenuContentObject.php:156
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\getCache
‪TYPO3 CMS Core Cache Frontend FrontendInterface getCache()
Definition: AbstractMenuContentObject.php:1861
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isItemState
‪bool isItemState($kind, $key)
Definition: AbstractMenuContentObject.php:1536
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\isSubMenu
‪bool isSubMenu($uid)
Definition: AbstractMenuContentObject.php:1473
‪TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject\link
‪array link($key, $altTarget, $typeOverride)
Definition: AbstractMenuContentObject.php:1188