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