TYPO3 CMS  TYPO3_8-7
AbstractMenuContentObject.php
Go to the documentation of this file.
1 <?php
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  (
358  !empty($rl_MParray)
359  ? ':' . implode(',', $rl_MParray)
360  : ''
361  );
362  // For normal mount points, set the variable for next level.
363  if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
364  $rl_MParray[] = $v_rl['_MP_PARAM'];
365  }
366  }
367  }
368  // Set $directoryLevel so the following evalution of the nextActive will not return
369  // an invalid value if .special=directory was set
370  $directoryLevel = 0;
371  if ($this->conf['special'] === 'directory') {
372  $value = isset($this->conf['special.']['value.']) ? $this->parent_cObj->stdWrap(
373  $this->conf['special.']['value'],
374  $this->conf['special.']['value.']
375  ) : $this->conf['special.']['value'];
376  if ($value === '') {
377  $value = $tsfe->page['uid'];
378  }
379  $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
380  }
381  // 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
382  // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
383  $startLevel = $directoryLevel ?: $this->entryLevel;
384  $currentLevel = $startLevel + $this->menuNumber;
385  if (is_array($this->tmpl->rootLine[$currentLevel])) {
386  $nextMParray = $this->MP_array;
387  if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
388  // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
389  // otherwise automatic expansion will not work
390  $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
391  if (isset($parentRecord['_MP_PARAM'])) {
392  $nextMParray[] = $parentRecord['_MP_PARAM'];
393  }
394  }
395  // In overlay mode, add next level MPvars as well:
396  if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
397  $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
398  }
399  $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
400  (
401  !empty($nextMParray)
402  ? ':' . implode(',', $nextMParray)
403  : ''
404  );
405  } else {
406  $this->nextActive = '';
407  }
408  // imgNamePrefix
409  if ($this->mconf['imgNamePrefix']) {
410  $this->imgNamePrefix = $this->mconf['imgNamePrefix'];
411  }
412  $this->imgNameNotRandom = $this->mconf['imgNameNotRandom'];
413  $retVal = true;
414  } else {
415  $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
416  $retVal = false;
417  }
418  return $retVal;
419  }
420 
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 
524  public function generate()
525  {
526  }
527 
531  public function writeMenu()
532  {
533  return '';
534  }
535 
542  protected function removeInaccessiblePages(array $pages)
543  {
544  $banned = $this->getBannedUids();
545  $filteredPages = [];
546  foreach ($pages as $aPage) {
547  if ($this->filterMenuPages($aPage, $banned, $aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
548  $filteredPages[$aPage['uid']] = $aPage;
549  }
550  }
551  return $filteredPages;
552  }
553 
559  protected function prepareMenuItems()
560  {
561  $menuItems = [];
562  $alternativeSortingField = trim($this->mconf['alternativeSortingField']) ?: 'sorting';
563 
564  // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
565  $additionalWhere = isset($this->mconf['additionalWhere']) ? $this->mconf['additionalWhere'] : '';
566  if (isset($this->mconf['additionalWhere.'])) {
567  $additionalWhere = $this->parent_cObj->stdWrap($additionalWhere, $this->mconf['additionalWhere.']);
568  }
569 
570  // ... only for the FIRST level of a HMENU
571  if ($this->menuNumber == 1 && $this->conf['special']) {
572  $value = isset($this->conf['special.']['value.'])
573  ? $this->parent_cObj->stdWrap($this->conf['special.']['value'], $this->conf['special.']['value.'])
574  : $this->conf['special.']['value'];
575  switch ($this->conf['special']) {
576  case 'userfunction':
577  $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
578  break;
579  case 'language':
580  $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
581  break;
582  case 'directory':
583  $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
584  break;
585  case 'list':
586  $menuItems = $this->prepareMenuItemsForListMenu($value);
587  break;
588  case 'updated':
589  $menuItems = $this->prepareMenuItemsForUpdatedMenu(
590  $value,
591  $this->mconf['alternativeSortingField'] ?: false
592  );
593  break;
594  case 'keywords':
595  $menuItems = $this->prepareMenuItemsForKeywordsMenu(
596  $value,
597  $this->mconf['alternativeSortingField'] ?: false
598  );
599  break;
600  case 'categories':
602  $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
603  $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
604  break;
605  case 'rootline':
606  $menuItems = $this->prepareMenuItemsForRootlineMenu();
607  break;
608  case 'browse':
609  $menuItems = $this->prepareMenuItemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
610  break;
611  }
612  if ($this->mconf['sectionIndex']) {
613  $sectionIndexes = [];
614  foreach ($menuItems as $page) {
615  $sectionIndexes = $sectionIndexes + $this->sectionIndex($alternativeSortingField, $page['uid']);
616  }
617  $menuItems = $sectionIndexes;
618  }
619  } elseif (is_array($this->alternativeMenuTempArray)) {
620  // Setting $menuItems array if not level 1.
621  $menuItems = $this->alternativeMenuTempArray;
622  } elseif ($this->mconf['sectionIndex']) {
623  $menuItems = $this->sectionIndex($alternativeSortingField);
624  } else {
625  // Default: Gets a hierarchical menu based on subpages of $this->id
626  $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
627  }
628  return $menuItems;
629  }
630 
638  protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
639  {
640  $menuItems = $this->parent_cObj->callUserFunction(
641  $this->conf['special.']['userFunc'],
642  array_merge($this->conf['special.'], ['value' => $specialValue, '_altSortField' => $sortingField]),
643  ''
644  );
645  if (!is_array($menuItems)) {
646  $menuItems = [];
647  }
648  return $menuItems;
649  }
650 
657  protected function prepareMenuItemsForLanguageMenu($specialValue)
658  {
659  $menuItems = [];
660  // Getting current page record NOT overlaid by any translation:
661  $tsfe = $this->getTypoScriptFrontendController();
662  $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->page['uid']);
663  // Traverse languages set up:
664  $languageItems = GeneralUtility::intExplode(',', $specialValue);
665  foreach ($languageItems as $sUid) {
666  // Find overlay record:
667  if ($sUid) {
668  $lRecs = $this->sys_page->getPageOverlay($tsfe->page['uid'], $sUid);
669  } else {
670  $lRecs = [];
671  }
672  // Checking if the "disabled" state should be set.
673  if (GeneralUtility::hideIfNotTranslated($tsfe->page['l18n_cfg']) && $sUid &&
674  empty($lRecs) || GeneralUtility::hideIfDefaultLanguage($tsfe->page['l18n_cfg']) &&
675  (!$sUid || empty($lRecs)) ||
676  !$this->conf['special.']['normalWhenNoLanguage'] && $sUid && empty($lRecs)
677  ) {
678  $iState = $tsfe->sys_language_uid == $sUid ? 'USERDEF2' : 'USERDEF1';
679  } else {
680  $iState = $tsfe->sys_language_uid == $sUid ? 'ACT' : 'NO';
681  }
682  if ($this->conf['addQueryString']) {
683  $getVars = $this->parent_cObj->getQueryArguments(
684  $this->conf['addQueryString.'],
685  ['L' => $sUid],
686  true
687  );
688  $this->analyzeCacheHashRequirements($getVars);
689  } else {
690  $getVars = '&L=' . $sUid;
691  }
692  // Adding menu item:
693  $menuItems[] = array_merge(
694  array_merge($currentPageWithNoOverlay, $lRecs),
695  [
696  'ITEM_STATE' => $iState,
697  '_ADD_GETVARS' => $getVars,
698  '_SAFE' => true
699  ]
700  );
701  }
702  return $menuItems;
703  }
704 
712  protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
713  {
714  $tsfe = $this->getTypoScriptFrontendController();
715  $menuItems = [];
716  if ($specialValue == '') {
717  $specialValue = $tsfe->page['uid'];
718  }
719  $items = GeneralUtility::intExplode(',', $specialValue);
720  foreach ($items as $id) {
721  $MP = $this->tmpl->getFromMPmap($id);
722  // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
723  $mount_info = $this->sys_page->getMountPointInfo($id);
724  if (is_array($mount_info)) {
725  if ($mount_info['overlay']) {
726  // Overlays should already have their full MPvars calculated:
727  $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
728  $MP = $MP ? $MP : $mount_info['MPvar'];
729  } else {
730  $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
731  }
732  $id = $mount_info['mount_pid'];
733  }
734  // Get sub-pages:
735  $statement = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
736  while ($row = $statement->fetch()) {
737  $tsfe->sys_page->versionOL('pages', $row, true);
738  if (!empty($row)) {
739  // Keep mount point?
740  $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
741  // There is a valid mount point.
742  if (is_array($mount_info) && $mount_info['overlay']) {
743  // Using "getPage" is OK since we need the check for enableFields
744  // AND for type 2 of mount pids we DO require a doktype < 200!
745  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
746  if (!empty($mp_row)) {
747  $row = $mp_row;
748  $row['_MP_PARAM'] = $mount_info['MPvar'];
749  } else {
750  // If the mount point could not be fetched with respect
751  // to enableFields, unset the row so it does not become a part of the menu!
752  unset($row);
753  }
754  }
755  // Add external MP params, then the row:
756  if (!empty($row)) {
757  if ($MP) {
758  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
759  }
760  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
761  }
762  }
763  }
764  }
765 
766  return $menuItems;
767  }
768 
775  protected function prepareMenuItemsForListMenu($specialValue)
776  {
777  $menuItems = [];
778  if ($specialValue == '') {
779  $specialValue = $this->id;
780  }
781  $skippedEnableFields = [];
782  if (!empty($this->mconf['showAccessRestrictedPages'])) {
783  $skippedEnableFields = ['fe_group' => 1];
784  }
786  $loadDB = GeneralUtility::makeInstance(RelationHandler::class);
787  $loadDB->setFetchAllFields(true);
788  $loadDB->start($specialValue, 'pages');
789  $loadDB->additionalWhere['pages'] = $this->parent_cObj->enableFields('pages', false, $skippedEnableFields);
790  $loadDB->getFromDB();
791  foreach ($loadDB->itemArray as $val) {
792  $MP = $this->tmpl->getFromMPmap($val['id']);
793  // Keep mount point?
794  $mount_info = $this->sys_page->getMountPointInfo($val['id']);
795  // There is a valid mount point.
796  if (is_array($mount_info) && $mount_info['overlay']) {
797  // Using "getPage" is OK since we need the check for enableFields
798  // AND for type 2 of mount pids we DO require a doktype < 200!
799  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
800  if (!empty($mp_row)) {
801  $row = $mp_row;
802  $row['_MP_PARAM'] = $mount_info['MPvar'];
803  // Overlays should already have their full MPvars calculated
804  if ($mount_info['overlay']) {
805  $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
806  if ($MP) {
807  unset($row['_MP_PARAM']);
808  }
809  }
810  } else {
811  // If the mount point could not be fetched with respect to
812  // enableFields, unset the row so it does not become a part of the menu!
813  unset($row);
814  }
815  } else {
816  $row = $loadDB->results['pages'][$val['id']];
817  }
818  // Add versioning overlay for current page (to respect workspaces)
819  if (isset($row) && is_array($row)) {
820  $this->sys_page->versionOL('pages', $row, true);
821  }
822  // Add external MP params, then the row:
823  if (isset($row) && is_array($row)) {
824  if ($MP) {
825  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
826  }
827  $menuItems[] = $this->sys_page->getPageOverlay($row);
828  }
829  }
830  return $menuItems;
831  }
832 
840  protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
841  {
842  $tsfe = $this->getTypoScriptFrontendController();
843  $menuItems = [];
844  if ($specialValue == '') {
845  $specialValue = $tsfe->page['uid'];
846  }
847  $items = GeneralUtility::intExplode(',', $specialValue);
848  if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
849  $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
850  } else {
851  $depth = 20;
852  }
853  // Max number of items
854  $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
855  $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
856  if (!$limit) {
857  $limit = 10;
858  }
859  // *'auto', 'manual', 'tstamp'
860  $mode = $this->conf['special.']['mode'];
861  // Get id's
862  $id_list_arr = [];
863  foreach ($items as $id) {
864  $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
865  $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $bA, $bA - 1);
866  }
867  $id_list = implode(',', $id_list_arr);
868  // Get sortField (mode)
869  switch ($mode) {
870  case 'starttime':
871  $sortField = 'starttime';
872  break;
873  case 'lastUpdated':
874  case 'manual':
875  $sortField = 'lastUpdated';
876  break;
877  case 'tstamp':
878  $sortField = 'tstamp';
879  break;
880  case 'crdate':
881  $sortField = 'crdate';
882  break;
883  default:
884  $sortField = 'SYS_LASTCHANGED';
885  }
886  $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
887  if ($this->conf['special.']['excludeNoSearchPages']) {
888  $extraWhere .= ' AND pages.no_search=0';
889  }
890  if ($maxAge > 0) {
891  $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
892  }
893  $statement = $this->parent_cObj->exec_getQuery('pages', [
894  'pidInList' => '0',
895  'uidInList' => $id_list,
896  'where' => $sortField . '>=0' . $extraWhere,
897  'orderBy' => $sortingField ?: $sortField . ' DESC',
898  'max' => $limit
899  ]);
900  while ($row = $statement->fetch()) {
901  $tsfe->sys_page->versionOL('pages', $row, true);
902  if (is_array($row)) {
903  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
904  }
905  }
906 
907  return $menuItems;
908  }
909 
917  protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
918  {
919  $tsfe = $this->getTypoScriptFrontendController();
920  $menuItems = [];
921  list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
922  if (!$specialValue) {
923  $specialValue = $tsfe->page['uid'];
924  }
925  if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
926  $kw = isset($this->conf['special.']['setKeywords.']) ? $this->parent_cObj->stdWrap($this->conf['special.']['setKeywords'], $this->conf['special.']['setKeywords.']) : $this->conf['special.']['setKeywords'];
927  } else {
928  // The page record of the 'value'.
929  $value_rec = $this->sys_page->getPage($specialValue);
930  $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
931  // keywords.
932  $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
933  }
934  // *'auto', 'manual', 'tstamp'
935  $mode = $this->conf['special.']['mode'];
936  switch ($mode) {
937  case 'starttime':
938  $sortField = 'starttime';
939  break;
940  case 'lastUpdated':
941  case 'manual':
942  $sortField = 'lastUpdated';
943  break;
944  case 'tstamp':
945  $sortField = 'tstamp';
946  break;
947  case 'crdate':
948  $sortField = 'crdate';
949  break;
950  default:
951  $sortField = 'SYS_LASTCHANGED';
952  }
953  // Depth, limit, extra where
954  if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
955  $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
956  } else {
957  $depth = 20;
958  }
959  // Max number of items
960  $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
961  // Start point
962  $eLevel = $this->parent_cObj->getKey(
963  isset($this->conf['special.']['entryLevel.'])
964  ? $this->parent_cObj->stdWrap($this->conf['special.']['entryLevel'], $this->conf['special.']['entryLevel.'])
965  : $this->conf['special.']['entryLevel'],
966  $this->tmpl->rootLine
967  );
968  $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
969  // Which field is for keywords
970  $kfield = 'keywords';
971  if ($this->conf['special.']['keywordsField']) {
972  list($kfield) = explode(' ', trim($this->conf['special.']['keywordsField']));
973  }
974  // If there are keywords and the startuid is present
975  if ($kw && $startUid) {
976  $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
977  $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
978  $kwArr = GeneralUtility::trimExplode(',', $kw, true);
979  $keyWordsWhereArr = [];
980  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
981  foreach ($kwArr as $word) {
982  $keyWordsWhereArr[] = $queryBuilder->expr()->like(
983  $kfield,
984  $queryBuilder->createNamedParameter(
985  '%' . $queryBuilder->escapeLikeWildcards($word) . '%',
986  \PDO::PARAM_STR
987  )
988  );
989  }
990  $queryBuilder
991  ->select('*')
992  ->from('pages')
993  ->where(
994  $queryBuilder->expr()->in(
995  'uid',
996  GeneralUtility::intExplode(',', $id_list, true)
997  ),
998  $queryBuilder->expr()->neq(
999  'uid',
1000  $queryBuilder->createNamedParameter($specialValue, \PDO::PARAM_INT)
1001  )
1002  );
1003 
1004  if (count($keyWordsWhereArr) !== 0) {
1005  $queryBuilder->andWhere($queryBuilder->expr()->orX(...$keyWordsWhereArr));
1006  }
1007 
1008  if ($this->doktypeExcludeList) {
1009  $queryBuilder->andWhere(
1010  $queryBuilder->expr()->notIn(
1011  'pages.doktype',
1012  GeneralUtility::intExplode(',', $this->doktypeExcludeList, true)
1013  )
1014  );
1015  }
1016 
1017  if (!$this->conf['includeNotInMenu']) {
1018  $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.nav_hide', 0));
1019  }
1020 
1021  if ($this->conf['special.']['excludeNoSearchPages']) {
1022  $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.no_search', 0));
1023  }
1024 
1025  if ($limit > 0) {
1026  $queryBuilder->setMaxResults($limit);
1027  }
1028 
1029  if ($sortingField) {
1030  $queryBuilder->orderBy($sortingField);
1031  } else {
1032  $queryBuilder->orderBy($sortField, 'desc');
1033  }
1034 
1035  $result = $queryBuilder->execute();
1036  while ($row = $result->fetch()) {
1037  $tsfe->sys_page->versionOL('pages', $row, true);
1038  if (is_array($row)) {
1039  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
1040  }
1041  }
1042  }
1043 
1044  return $menuItems;
1045  }
1046 
1052  protected function prepareMenuItemsForRootlineMenu()
1053  {
1054  $menuItems = [];
1055  $range = isset($this->conf['special.']['range.'])
1056  ? $this->parent_cObj->stdWrap($this->conf['special.']['range'], $this->conf['special.']['range.'])
1057  : $this->conf['special.']['range'];
1058  $begin_end = explode('|', $range);
1059  $begin_end[0] = (int)$begin_end[0];
1060  if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1061  $begin_end[1] = -1;
1062  }
1063  $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1064  $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1065  if ($endKey < $beginKey) {
1066  $endKey = $beginKey;
1067  }
1068  $rl_MParray = [];
1069  foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1070  // For overlaid mount points, set the variable right now:
1071  if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1072  $rl_MParray[] = $v_rl['_MP_PARAM'];
1073  }
1074  // Traverse rootline:
1075  if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1076  $temp_key = $k_rl;
1077  $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1078  if (!empty($menuItems[$temp_key])) {
1079  // If there are no specific target for the page, put the level specific target on.
1080  if (!$menuItems[$temp_key]['target']) {
1081  $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1082  $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1083  }
1084  } else {
1085  unset($menuItems[$temp_key]);
1086  }
1087  }
1088  // For normal mount points, set the variable for next level.
1089  if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1090  $rl_MParray[] = $v_rl['_MP_PARAM'];
1091  }
1092  }
1093  // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1094  if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1095  $menuItems = array_reverse($menuItems);
1096  }
1097  return $menuItems;
1098  }
1099 
1108  protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1109  {
1110  $menuItems = [];
1111  list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
1112  if (!$specialValue) {
1113  $specialValue = $this->getTypoScriptFrontendController()->page['uid'];
1114  }
1115  // Will not work out of rootline
1116  if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1117  $recArr = [];
1118  // The page record of the 'value'.
1119  $value_rec = $this->sys_page->getPage($specialValue);
1120  // 'up' page cannot be outside rootline
1121  if ($value_rec['pid']) {
1122  // The page record of 'up'.
1123  $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1124  }
1125  // If the 'up' item was NOT level 0 in rootline...
1126  if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1127  // The page record of "index".
1128  $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1129  }
1130  // check if certain pages should be excluded
1131  $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1132  if ($this->conf['special.']['excludeNoSearchPages']) {
1133  $additionalWhere .= ' AND pages.no_search=0';
1134  }
1135  // prev / next is found
1136  $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1137  $lastKey = 0;
1138  $nextActive = 0;
1139  foreach ($prevnext_menu as $k_b => $v_b) {
1140  if ($nextActive) {
1141  $recArr['next'] = $v_b;
1142  $nextActive = 0;
1143  }
1144  if ($v_b['uid'] == $specialValue) {
1145  if ($lastKey) {
1146  $recArr['prev'] = $prevnext_menu[$lastKey];
1147  }
1148  $nextActive = 1;
1149  }
1150  $lastKey = $k_b;
1151  }
1152 
1153  $recArr['first'] = reset($prevnext_menu);
1154  $recArr['last'] = end($prevnext_menu);
1155  // prevsection / nextsection is found
1156  // You can only do this, if there is a valid page two levels up!
1157  if (!empty($recArr['index']['uid'])) {
1158  $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1159  $lastKey = 0;
1160  $nextActive = 0;
1161  foreach ($prevnextsection_menu as $k_b => $v_b) {
1162  if ($nextActive) {
1163  $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1164  if (!empty($sectionRec_temp)) {
1165  $recArr['nextsection'] = reset($sectionRec_temp);
1166  $recArr['nextsection_last'] = end($sectionRec_temp);
1167  $nextActive = 0;
1168  }
1169  }
1170  if ($v_b['uid'] == $value_rec['pid']) {
1171  if ($lastKey) {
1172  $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1173  if (!empty($sectionRec_temp)) {
1174  $recArr['prevsection'] = reset($sectionRec_temp);
1175  $recArr['prevsection_last'] = end($sectionRec_temp);
1176  }
1177  }
1178  $nextActive = 1;
1179  }
1180  $lastKey = $k_b;
1181  }
1182  }
1183  if ($this->conf['special.']['items.']['prevnextToSection']) {
1184  if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1185  $recArr['prev'] = $recArr['prevsection_last'];
1186  }
1187  if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1188  $recArr['next'] = $recArr['nextsection'];
1189  }
1190  }
1191  $items = explode('|', $this->conf['special.']['items']);
1192  $c = 0;
1193  foreach ($items as $k_b => $v_b) {
1194  $v_b = strtolower(trim($v_b));
1195  if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1196  $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1197  }
1198  if (is_array($recArr[$v_b])) {
1199  $menuItems[$c] = $recArr[$v_b];
1200  if ($this->conf['special.'][$v_b . '.']['target']) {
1201  $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1202  }
1203  $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1204  if (is_array($tmpSpecialFields)) {
1205  foreach ($tmpSpecialFields as $fk => $val) {
1206  $menuItems[$c][$fk] = $val;
1207  }
1208  }
1209  $c++;
1210  }
1211  }
1212  }
1213  return $menuItems;
1214  }
1215 
1221  protected function analyzeCacheHashRequirements($queryString)
1222  {
1223  $parameters = GeneralUtility::explodeUrl2Array($queryString);
1224  if (!empty($parameters)) {
1225  if (!isset($parameters['id'])) {
1226  $queryString .= '&id=' . $this->getTypoScriptFrontendController()->id;
1227  }
1229  $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
1230  $cHashParameters = $cacheHashCalculator->getRelevantParameters($queryString);
1231  if (count($cHashParameters) > 1) {
1232  $this->useCacheHash = (
1233  $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ||
1234  !isset($parameters['no_cache']) ||
1235  !$parameters['no_cache']
1236  );
1237  }
1238  }
1239  }
1240 
1252  public function filterMenuPages(&$data, $banUidArray, $spacer)
1253  {
1254  $includePage = true;
1255  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'])) {
1256  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] as $classRef) {
1257  $hookObject = GeneralUtility::getUserObj($classRef);
1258  if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1259  throw new \UnexpectedValueException($classRef . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1260  }
1261  $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $spacer, $this);
1262  }
1263  }
1264  if (!$includePage) {
1265  return false;
1266  }
1267  if ($data['_SAFE']) {
1268  return true;
1269  }
1270 
1271  if (
1272  ($this->mconf['SPC'] || !$spacer) // If the spacer-function is not enabled, spacers will not enter the $menuArr
1273  && (!$data['nav_hide'] || $this->conf['includeNotInMenu']) // Not hidden in navigation
1274  && !GeneralUtility::inList($this->doktypeExcludeList, $data['doktype']) // Page may not be 'not_in_menu' or 'Backend User Section'
1275  && !in_array($data['uid'], $banUidArray, false) // not in banned uid's
1276  ) {
1277  // Checks if the default language version can be shown:
1278  // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
1279  $tsfe = $this->getTypoScriptFrontendController();
1280  $blockPage = GeneralUtility::hideIfDefaultLanguage($data['l18n_cfg']) && (!$tsfe->sys_language_uid || $tsfe->sys_language_uid && !$data['_PAGES_OVERLAY']);
1281  if (!$blockPage) {
1282  // Checking if a page should be shown in the menu depending on whether a translation exists:
1283  $tok = true;
1284  // There is an alternative language active AND the current page requires a translation:
1285  if ($tsfe->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1286  if (!$data['_PAGES_OVERLAY']) {
1287  $tok = false;
1288  }
1289  }
1290  // Continue if token is TRUE:
1291  if ($tok) {
1292  // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1293  if ($this->conf['protectLvar']) {
1294  $languageUid = (int)$tsfe->config['config']['sys_language_uid'];
1295  if ($languageUid && ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
1296  $olRec = $tsfe->sys_page->getPageOverlay($data['uid'], $languageUid);
1297  if (empty($olRec)) {
1298  // If no pages_language_overlay record then page can NOT be accessed in
1299  // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1300  $data['_ADD_GETVARS'] .= '&L=0';
1301  }
1302  }
1303  }
1304  return true;
1305  }
1306  }
1307  }
1308  return false;
1309  }
1310 
1326  public function procesItemStates($splitCount)
1327  {
1328  // Prepare normal settings
1329  if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1330  // Setting a blank array if NO=1 and there are no properties.
1331  $this->mconf['NO.'] = [];
1332  }
1333  $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1334  $NOconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['NO.'], $splitCount);
1335  // Prepare rollOver settings, overriding normal settings
1336  $ROconf = [];
1337  if ($this->mconf['RO']) {
1338  $ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['RO.'], $splitCount);
1339  }
1340  // Prepare IFSUB settings, overriding normal settings
1341  // IFSUB is TRUE if there exist submenu items to the current item
1342  if (!empty($this->mconf['IFSUB'])) {
1343  $IFSUBconf = null;
1344  $IFSUBROconf = null;
1345  foreach ($NOconf as $key => $val) {
1346  if ($this->isItemState('IFSUB', $key)) {
1347  // if this is the first IFSUB element, we must generate IFSUB.
1348  if ($IFSUBconf === null) {
1349  $IFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['IFSUB.'], $splitCount);
1350  if (!empty($this->mconf['IFSUBRO'])) {
1351  $IFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['IFSUBRO.'], $splitCount);
1352  }
1353  }
1354  // Substitute normal with ifsub
1355  if (isset($IFSUBconf[$key])) {
1356  $NOconf[$key] = $IFSUBconf[$key];
1357  }
1358  // If rollOver on normal, we must apply a state for rollOver on the active
1359  if ($ROconf) {
1360  // If RollOver on active then apply this
1361  $ROconf[$key] = !empty($IFSUBROconf[$key]) ? $IFSUBROconf[$key] : $IFSUBconf[$key];
1362  }
1363  }
1364  }
1365  }
1366  // Prepare active settings, overriding normal settings
1367  if (!empty($this->mconf['ACT'])) {
1368  $ACTconf = null;
1369  $ACTROconf = null;
1370  // Find active
1371  foreach ($NOconf as $key => $val) {
1372  if ($this->isItemState('ACT', $key)) {
1373  // If this is the first 'active', we must generate ACT.
1374  if ($ACTconf === null) {
1375  $ACTconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACT.'], $splitCount);
1376  // Prepare active rollOver settings, overriding normal active settings
1377  if (!empty($this->mconf['ACTRO'])) {
1378  $ACTROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTRO.'], $splitCount);
1379  }
1380  }
1381  // Substitute normal with active
1382  if (isset($ACTconf[$key])) {
1383  $NOconf[$key] = $ACTconf[$key];
1384  }
1385  // If rollOver on normal, we must apply a state for rollOver on the active
1386  if ($ROconf) {
1387  // If RollOver on active then apply this
1388  $ROconf[$key] = !empty($ACTROconf[$key]) ? $ACTROconf[$key] : $ACTconf[$key];
1389  }
1390  }
1391  }
1392  }
1393  // Prepare ACT (active)/IFSUB settings, overriding normal settings
1394  // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
1395  if (!empty($this->mconf['ACTIFSUB'])) {
1396  $ACTIFSUBconf = null;
1397  $ACTIFSUBROconf = null;
1398  // Find active
1399  foreach ($NOconf as $key => $val) {
1400  if ($this->isItemState('ACTIFSUB', $key)) {
1401  // If this is the first 'active', we must generate ACTIFSUB.
1402  if ($ACTIFSUBconf === null) {
1403  $ACTIFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTIFSUB.'], $splitCount);
1404  // Prepare active rollOver settings, overriding normal active settings
1405  if (!empty($this->mconf['ACTIFSUBRO'])) {
1406  $ACTIFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTIFSUBRO.'], $splitCount);
1407  }
1408  }
1409  // Substitute normal with active
1410  if (isset($ACTIFSUBconf[$key])) {
1411  $NOconf[$key] = $ACTIFSUBconf[$key];
1412  }
1413  // If rollOver on normal, we must apply a state for rollOver on the active
1414  if ($ROconf) {
1415  // If RollOver on active then apply this
1416  $ROconf[$key] = !empty($ACTIFSUBROconf[$key]) ? $ACTIFSUBROconf[$key] : $ACTIFSUBconf[$key];
1417  }
1418  }
1419  }
1420  }
1421  // Prepare CUR (current) settings, overriding normal settings
1422  // CUR is TRUE if the current page equals the item here!
1423  if (!empty($this->mconf['CUR'])) {
1424  $CURconf = null;
1425  $CURROconf = null;
1426  foreach ($NOconf as $key => $val) {
1427  if ($this->isItemState('CUR', $key)) {
1428  // if this is the first 'current', we must generate CUR. Basically this control is just inherited
1429  // from the other implementations as current would only exist one time and that's it
1430  // (unless you use special-features of HMENU)
1431  if ($CURconf === null) {
1432  $CURconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CUR.'], $splitCount);
1433  if (!empty($this->mconf['CURRO'])) {
1434  $CURROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURRO.'], $splitCount);
1435  }
1436  }
1437  // Substitute normal with current
1438  if (isset($CURconf[$key])) {
1439  $NOconf[$key] = $CURconf[$key];
1440  }
1441  // If rollOver on normal, we must apply a state for rollOver on the active
1442  if ($ROconf) {
1443  // If RollOver on active then apply this
1444  $ROconf[$key] = !empty($CURROconf[$key]) ? $CURROconf[$key] : $CURconf[$key];
1445  }
1446  }
1447  }
1448  }
1449  // Prepare CUR (current)/IFSUB settings, overriding normal settings
1450  // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
1451  if (!empty($this->mconf['CURIFSUB'])) {
1452  $CURIFSUBconf = null;
1453  $CURIFSUBROconf = null;
1454  foreach ($NOconf as $key => $val) {
1455  if ($this->isItemState('CURIFSUB', $key)) {
1456  // If this is the first 'current', we must generate CURIFSUB.
1457  if ($CURIFSUBconf === null) {
1458  $CURIFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURIFSUB.'], $splitCount);
1459  // Prepare current rollOver settings, overriding normal current settings
1460  if (!empty($this->mconf['CURIFSUBRO'])) {
1461  $CURIFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURIFSUBRO.'], $splitCount);
1462  }
1463  }
1464  // Substitute normal with active
1465  if ($CURIFSUBconf[$key]) {
1466  $NOconf[$key] = $CURIFSUBconf[$key];
1467  }
1468  // If rollOver on normal, we must apply a state for rollOver on the current
1469  if ($ROconf) {
1470  // If RollOver on current then apply this
1471  $ROconf[$key] = !empty($CURIFSUBROconf[$key]) ? $CURIFSUBROconf[$key] : $CURIFSUBconf[$key];
1472  }
1473  }
1474  }
1475  }
1476  // Prepare active settings, overriding normal settings
1477  if (!empty($this->mconf['USR'])) {
1478  $USRconf = null;
1479  $USRROconf = null;
1480  // Find active
1481  foreach ($NOconf as $key => $val) {
1482  if ($this->isItemState('USR', $key)) {
1483  // if this is the first active, we must generate USR.
1484  if ($USRconf === null) {
1485  $USRconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USR.'], $splitCount);
1486  // Prepare active rollOver settings, overriding normal active settings
1487  if (!empty($this->mconf['USRRO'])) {
1488  $USRROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USRRO.'], $splitCount);
1489  }
1490  }
1491  // Substitute normal with active
1492  if ($USRconf[$key]) {
1493  $NOconf[$key] = $USRconf[$key];
1494  }
1495  // If rollOver on normal, we must apply a state for rollOver on the active
1496  if ($ROconf) {
1497  // If RollOver on active then apply this
1498  $ROconf[$key] = !empty($USRROconf[$key]) ? $USRROconf[$key] : $USRconf[$key];
1499  }
1500  }
1501  }
1502  }
1503  // Prepare spacer settings, overriding normal settings
1504  if (!empty($this->mconf['SPC'])) {
1505  $SPCconf = null;
1506  // Find spacers
1507  foreach ($NOconf as $key => $val) {
1508  if ($this->isItemState('SPC', $key)) {
1509  // If this is the first spacer, we must generate SPC.
1510  if ($SPCconf === null) {
1511  $SPCconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['SPC.'], $splitCount);
1512  }
1513  // Substitute normal with spacer
1514  if (isset($SPCconf[$key])) {
1515  $NOconf[$key] = $SPCconf[$key];
1516  }
1517  }
1518  }
1519  }
1520  // Prepare Userdefined settings
1521  if (!empty($this->mconf['USERDEF1'])) {
1522  $USERDEF1conf = null;
1523  $USERDEF1ROconf = null;
1524  // Find active
1525  foreach ($NOconf as $key => $val) {
1526  if ($this->isItemState('USERDEF1', $key)) {
1527  // If this is the first active, we must generate USERDEF1.
1528  if ($USERDEF1conf === null) {
1529  $USERDEF1conf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF1.'], $splitCount);
1530  // Prepare active rollOver settings, overriding normal active settings
1531  if (!empty($this->mconf['USERDEF1RO'])) {
1532  $USERDEF1ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF1RO.'], $splitCount);
1533  }
1534  }
1535  // Substitute normal with active
1536  if (isset($USERDEF1conf[$key])) {
1537  $NOconf[$key] = $USERDEF1conf[$key];
1538  }
1539  // If rollOver on normal, we must apply a state for rollOver on the active
1540  if ($ROconf) {
1541  // If RollOver on active then apply this
1542  $ROconf[$key] = !empty($USERDEF1ROconf[$key]) ? $USERDEF1ROconf[$key] : $USERDEF1conf[$key];
1543  }
1544  }
1545  }
1546  }
1547  // Prepare Userdefined settings
1548  if (!empty($this->mconf['USERDEF2'])) {
1549  $USERDEF2conf = null;
1550  $USERDEF2ROconf = null;
1551  // Find active
1552  foreach ($NOconf as $key => $val) {
1553  if ($this->isItemState('USERDEF2', $key)) {
1554  // If this is the first active, we must generate USERDEF2.
1555  if ($USERDEF2conf === null) {
1556  $USERDEF2conf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF2.'], $splitCount);
1557  // Prepare active rollOver settings, overriding normal active settings
1558  if (!empty($this->mconf['USERDEF2RO'])) {
1559  $USERDEF2ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF2RO.'], $splitCount);
1560  }
1561  }
1562  // Substitute normal with active
1563  if (isset($USERDEF2conf[$key])) {
1564  $NOconf[$key] = $USERDEF2conf[$key];
1565  }
1566  // If rollOver on normal, we must apply a state for rollOver on the active
1567  if ($ROconf) {
1568  // If RollOver on active then apply this
1569  $ROconf[$key] = !empty($USERDEF2ROconf[$key]) ? $USERDEF2ROconf[$key] : $USERDEF2conf[$key];
1570  }
1571  }
1572  }
1573  }
1574  return [$NOconf, $ROconf];
1575  }
1576 
1587  public function link($key, $altTarget = '', $typeOverride = '')
1588  {
1589  $runtimeCache = $this->getRuntimeCache();
1590  $MP_var = $this->getMPvar($key);
1591  $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . $MP_var . serialize($this->menuArr[$key]));
1592  $runtimeCachedLink = $runtimeCache->get($cacheId);
1593  if ($runtimeCachedLink !== false) {
1594  return $runtimeCachedLink;
1595  }
1596 
1597  // Mount points:
1598  $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1599  // Setting override ID
1600  if ($this->mconf['overrideId'] || $this->menuArr[$key]['overrideId']) {
1601  $overrideArray = [];
1602  // If a user script returned the value overrideId in the menu array we use that as page id
1603  $overrideArray['uid'] = $this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId'];
1604  $overrideArray['alias'] = '';
1605  // Clear MP parameters since ID was changed.
1606  $MP_params = '';
1607  } else {
1608  $overrideArray = '';
1609  }
1610  // Setting main target:
1611  if ($altTarget) {
1612  $mainTarget = $altTarget;
1613  } elseif ($this->mconf['target.']) {
1614  $mainTarget = $this->parent_cObj->stdWrap($this->mconf['target'], $this->mconf['target.']);
1615  } else {
1616  $mainTarget = $this->mconf['target'];
1617  }
1618  // Creating link:
1619  $addParams = $this->mconf['addParams'] . $MP_params;
1620  if ($this->mconf['collapse'] && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1621  $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1622  $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1623  $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1624  } else {
1625  $addParams .= $this->I['val']['additionalParams'] . $this->menuArr[$key]['_ADD_GETVARS'];
1626  $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1627  }
1628  // Override default target configuration if option is set
1629  if ($this->menuArr[$key]['target']) {
1630  $LD['target'] = $this->menuArr[$key]['target'];
1631  }
1632 
1633  $tsfe = $this->getTypoScriptFrontendController();
1634 
1635  // Override URL if using "External URL"
1636  if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_LINK) {
1637  if ($this->menuArr[$key]['urltype'] == 3 && GeneralUtility::validEmail($this->menuArr[$key]['url'])) {
1638  // Create mailto-link using \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink (concerning spamProtectEmailAddresses):
1639  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $this->menuArr[$key]['url']]);
1640  $LD['target'] = '';
1641  } else {
1642  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $this->getSysPage()->getExtURL($this->menuArr[$key])]);
1643  if (empty($LD['target']) && !empty($tsfe->extTarget)) {
1644  $LD['target'] = $tsfe->extTarget;
1645  }
1646  }
1647  }
1648 
1649  // Override url if current page is a shortcut
1650  $shortcut = null;
1651  if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_SHORTCUT && $this->menuArr[$key]['shortcut_mode'] != PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1652  $menuItem = $this->determineOriginalShortcutPage($this->menuArr[$key]);
1653  try {
1654  $shortcut = $tsfe->getPageShortcut(
1655  $menuItem['shortcut'],
1656  $menuItem['shortcut_mode'],
1657  $menuItem['uid'],
1658  20,
1659  [],
1660  true
1661  );
1662  } catch (\Exception $ex) {
1663  }
1664  if (!is_array($shortcut)) {
1665  $runtimeCache->set($cacheId, []);
1666  return [];
1667  }
1668  // Only setting url, not target
1669  $LD['totalURL'] = $this->parent_cObj->typoLink_URL([
1670  'parameter' => $shortcut['uid'],
1671  'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1672  'linkAccessRestrictedPages' => !empty($this->mconf['showAccessRestrictedPages'])
1673  ]);
1674  }
1675  if ($shortcut) {
1676  $pageData = $shortcut;
1677  $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1678  } else {
1679  $pageData = $this->menuArr[$key];
1680  }
1681  // Manipulation in case of access restricted pages:
1682  $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1683  // Overriding URL / Target if set to do so:
1684  if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
1685  $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1686  if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1687  $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1688  }
1689  }
1690  // OnClick open in windows.
1691  $onClick = '';
1692  if ($this->mconf['JSWindow']) {
1693  $conf = $this->mconf['JSWindow.'];
1694  $url = $LD['totalURL'];
1695  $LD['totalURL'] = '#';
1696  $onClick = 'openPic('
1697  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1698  . '\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1699  . GeneralUtility::quoteJSvalue($conf['params']) . '); return false;';
1700  $tsfe->setJS('openPic');
1701  }
1702  // look for type and popup
1703  // following settings are valid in field target:
1704  // 230 will add type=230 to the link
1705  // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1706  // 230 _blank will add type=230 to the link and open with target "_blank"
1707  // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1708  $matches = [];
1709  $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1710  if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) || $targetIsType) {
1711  // has type?
1712  if ((int)$matches[1] || $targetIsType) {
1713  $LD['totalURL'] .= (strpos($LD['totalURL'], '?') === false ? '?' : '&') . 'type=' . ($targetIsType ?: (int)$matches[1]);
1714  $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1715  }
1716  // Open in popup window?
1717  if ($matches[3] && $matches[4]) {
1718  $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1719  $onClick = 'vHWin=window.open('
1720  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1721  . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1722  $LD['target'] = '';
1723  }
1724  }
1725  // out:
1726  $list = [];
1727  // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1728  // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1729  // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1730  $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1731  $list['TARGET'] = $LD['target'];
1732  $list['onClick'] = $onClick;
1733  $runtimeCache->set($cacheId, $list);
1734  return $list;
1735  }
1736 
1746  protected function determineOriginalShortcutPage(array $page)
1747  {
1748  // Check if modification is required
1749  if (
1750  $this->getTypoScriptFrontendController()->sys_language_uid > 0
1751  && empty($page['shortcut'])
1752  && !empty($page['uid'])
1753  && !empty($page['_PAGES_OVERLAY'])
1754  && !empty($page['_PAGES_OVERLAY_UID'])
1755  ) {
1756  // Using raw record since the record was overlaid and is correct already:
1757  $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1758 
1759  if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1760  $page['shortcut'] = $originalPage['shortcut'];
1761  }
1762  }
1763 
1764  return $page;
1765  }
1766 
1775  public function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1776  {
1777  // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1778  if ($this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1779  $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1780  $addParams = str_replace(
1781  [
1782  '###RETURN_URL###',
1783  '###PAGE_ID###'
1784  ],
1785  [
1786  rawurlencode($LD['totalURL']),
1787  isset($page['_SHORTCUT_PAGE_UID']) ? $page['_SHORTCUT_PAGE_UID'] : $page['uid']
1788  ],
1789  $this->mconf['showAccessRestrictedPages.']['addParams']
1790  );
1791  $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', '', $addParams, $typeOverride);
1792  }
1793  }
1794 
1803  public function subMenu($uid, $objSuffix = '')
1804  {
1805  // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1806  $altArray = '';
1807  if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU']) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1808  $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1809  }
1810  // Make submenu if the page is the next active
1811  $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix];
1812  // stdWrap for expAll
1813  if (isset($this->mconf['expAll.'])) {
1814  $this->mconf['expAll'] = $this->parent_cObj->stdWrap($this->mconf['expAll'], $this->mconf['expAll.']);
1815  }
1816  if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !$this->mconf['sectionIndex']) {
1817  try {
1818  $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1820  $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1821  $submenu->entryLevel = $this->entryLevel + 1;
1822  $submenu->rL_uidRegister = $this->rL_uidRegister;
1823  $submenu->MP_array = $this->MP_array;
1824  if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1825  $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1826  }
1827  // Especially scripts that build the submenu needs the parent data
1828  $submenu->parent_cObj = $this->parent_cObj;
1829  $submenu->setParentMenu($this->menuArr, $this->I['key']);
1830  // Setting alternativeMenuTempArray (will be effective only if an array)
1831  if (is_array($altArray)) {
1832  $submenu->alternativeMenuTempArray = $altArray;
1833  }
1834  if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1835  $submenu->makeMenu();
1836  // Memorize the current menu item count
1837  $tsfe = $this->getTypoScriptFrontendController();
1838  $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1839  // Reset the menu item count for the submenu
1840  $tsfe->register['count_MENUOBJ'] = 0;
1841  $content = $submenu->writeMenu();
1842  // Restore the item count now that the submenu has been handled
1843  $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1844  $tsfe->register['count_menuItems'] = count($this->menuArr);
1845  return $content;
1846  }
1847  } catch (Exception\NoSuchMenuTypeException $e) {
1848  }
1849  }
1850  return '';
1851  }
1852 
1862  public function isNext($uid, $MPvar = '')
1863  {
1864  // Check for always active PIDs:
1865  if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1866  return true;
1867  }
1868  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1869  if ($uid && $testUid == $this->nextActive) {
1870  return true;
1871  }
1872  return false;
1873  }
1874 
1883  public function isActive($uid, $MPvar = '')
1884  {
1885  // Check for always active PIDs:
1886  if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1887  return true;
1888  }
1889  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1890  if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1891  return true;
1892  }
1893  return false;
1894  }
1895 
1904  public function isCurrent($uid, $MPvar = '')
1905  {
1906  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1907  return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1908  }
1909 
1918  public function isSubMenu($uid)
1919  {
1920  $cacheId = 'menucontentobject-is-submenu-decision-' . $uid;
1921  $runtimeCache = $this->getRuntimeCache();
1922  $cachedDecision = $runtimeCache->get($cacheId);
1923  if (isset($cachedDecision['result'])) {
1924  return $cachedDecision['result'];
1925  }
1926  // Looking for a mount-pid for this UID since if that
1927  // exists we should look for a subpages THERE and not in the input $uid;
1928  $mount_info = $this->sys_page->getMountPointInfo($uid);
1929  if (is_array($mount_info)) {
1930  $uid = $mount_info['mount_pid'];
1931  }
1932  $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1933  $hasSubPages = false;
1934  $bannedUids = $this->getBannedUids();
1935  foreach ($recs as $theRec) {
1936  // no valid subpage if the document type is excluded from the menu
1937  if (GeneralUtility::inList($this->doktypeExcludeList, $theRec['doktype'])) {
1938  continue;
1939  }
1940  // No valid subpage if the page is hidden inside menus and
1941  // it wasn't forced to show such entries
1942  if ($theRec['nav_hide'] && !$this->conf['includeNotInMenu']) {
1943  continue;
1944  }
1945  // No valid subpage if the default language should be shown and the page settings
1946  // are excluding the visibility of the default language
1947  if (!$this->getTypoScriptFrontendController()->sys_language_uid && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
1948  continue;
1949  }
1950  // No valid subpage if the alternative language should be shown and the page settings
1951  // are requiring a valid overlay but it doesn't exists
1952  $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg']);
1953  if ($this->getTypoScriptFrontendController()->sys_language_uid && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1954  continue;
1955  }
1956  // No valid subpage if the subpage is banned by excludeUidList
1957  if (in_array($theRec['uid'], $bannedUids)) {
1958  continue;
1959  }
1960  $hasSubPages = true;
1961  break;
1962  }
1963  $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1964  return $hasSubPages;
1965  }
1966 
1976  public function isItemState($kind, $key)
1977  {
1978  $natVal = false;
1979  // If any value is set for ITEM_STATE the normal evaluation is discarded
1980  if ($this->menuArr[$key]['ITEM_STATE']) {
1981  if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1982  $natVal = true;
1983  }
1984  } else {
1985  switch ($kind) {
1986  case 'SPC':
1987  $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1988  break;
1989  case 'IFSUB':
1990  $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1991  break;
1992  case 'ACT':
1993  $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1994  break;
1995  case 'ACTIFSUB':
1996  $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1997  break;
1998  case 'CUR':
1999  $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
2000  break;
2001  case 'CURIFSUB':
2002  $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
2003  break;
2004  case 'USR':
2005  $natVal = (bool)$this->menuArr[$key]['fe_group'];
2006  break;
2007  }
2008  }
2009  return $natVal;
2010  }
2011 
2019  public function accessKey($title)
2020  {
2021  $tsfe = $this->getTypoScriptFrontendController();
2022  // The global array ACCESSKEY is used to globally control if letters are already used!!
2023  $result = [];
2024  $title = trim(strip_tags($title));
2025  $titleLen = strlen($title);
2026  for ($a = 0; $a < $titleLen; $a++) {
2027  $key = strtoupper(substr($title, $a, 1));
2028  if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
2029  $tsfe->accessKey[$key] = 1;
2030  $result['code'] = ' accesskey="' . $key . '"';
2031  $result['alt'] = ' (ALT+' . $key . ')';
2032  $result['key'] = $key;
2033  break;
2034  }
2035  }
2036  return $result;
2037  }
2038 
2048  public function userProcess($mConfKey, $passVar)
2049  {
2050  if ($this->mconf[$mConfKey]) {
2051  $funcConf = $this->mconf[$mConfKey . '.'];
2052  $funcConf['parentObj'] = $this;
2053  $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
2054  }
2055  return $passVar;
2056  }
2057 
2063  public function setATagParts()
2064  {
2065  $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
2066  $params = $params !== '' ? ' ' . $params : '';
2067  $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], true) . $params . '>';
2068  $this->I['A2'] = '</a>';
2069  }
2070 
2079  public function getPageTitle($title, $nav_title)
2080  {
2081  return trim($nav_title) !== '' ? $nav_title : $title;
2082  }
2083 
2091  public function getMPvar($key)
2092  {
2093  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
2094  $localMP_array = $this->MP_array;
2095  // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
2096  if ($this->menuArr[$key]['_MP_PARAM']) {
2097  $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
2098  }
2099  return !empty($localMP_array) ? implode(',', $localMP_array) : '';
2100  }
2101  return '';
2102  }
2103 
2110  public function getDoktypeExcludeWhere()
2111  {
2112  return $this->doktypeExcludeList ? ' AND pages.doktype NOT IN (' . $this->doktypeExcludeList . ')' : '';
2113  }
2114 
2121  public function getBannedUids()
2122  {
2123  $excludeUidList = isset($this->conf['excludeUidList.'])
2124  ? $this->parent_cObj->stdWrap($this->conf['excludeUidList'], $this->conf['excludeUidList.'])
2125  : $this->conf['excludeUidList'];
2126 
2127  if (!trim($excludeUidList)) {
2128  return [];
2129  }
2130 
2131  $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page['uid'], $excludeUidList);
2132  return GeneralUtility::intExplode(',', $banUidList);
2133  }
2134 
2147  public function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '')
2148  {
2149  $conf = [
2150  'parameter' => is_array($overrideArray) && $overrideArray['uid'] ? $overrideArray['uid'] : $page['uid']
2151  ];
2152  if (MathUtility::canBeInterpretedAsInteger($typeOverride)) {
2153  $conf['parameter'] .= ',' . (int)$typeOverride;
2154  }
2155  if ($addParams) {
2156  $conf['additionalParams'] = $addParams;
2157  }
2158  if ($no_cache) {
2159  $conf['no_cache'] = true;
2160  } elseif ($this->useCacheHash) {
2161  $conf['useCacheHash'] = true;
2162  }
2163  if ($oTarget) {
2164  $conf['target'] = $oTarget;
2165  }
2166  if ($page['sectionIndex_uid']) {
2167  $conf['section'] = $page['sectionIndex_uid'];
2168  }
2169  $conf['linkAccessRestrictedPages'] = !empty($this->mconf['showAccessRestrictedPages']);
2170  $this->parent_cObj->typoLink('|', $conf);
2171  $LD = $this->parent_cObj->lastTypoLinkLD;
2172  $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
2173  return $LD;
2174  }
2175 
2187  protected function sectionIndex($altSortField, $pid = null)
2188  {
2189  $pid = (int)($pid ?: $this->id);
2190  $basePageRow = $this->sys_page->getPage($pid);
2191  if (!is_array($basePageRow)) {
2192  return [];
2193  }
2194  $tsfe = $this->getTypoScriptFrontendController();
2195  $configuration = $this->mconf['sectionIndex.'];
2196  $useColPos = 0;
2197  if (trim($configuration['useColPos']) !== '' || is_array($configuration['useColPos.'])) {
2198  $useColPos = $tsfe->cObj->stdWrap($configuration['useColPos'], $configuration['useColPos.']);
2199  $useColPos = (int)$useColPos;
2200  }
2201  $selectSetup = [
2202  'pidInList' => $pid,
2203  'orderBy' => $altSortField,
2204  'languageField' => 'sys_language_uid',
2205  'where' => ''
2206  ];
2207 
2208  if ($useColPos >= 0) {
2209  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2210  ->getConnectionForTable('tt_content')
2211  ->getExpressionBuilder();
2212  $selectSetup['where'] = $expressionBuilder->eq('colPos', $useColPos);
2213  }
2214 
2215  if ($basePageRow['content_from_pid']) {
2216  // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
2217  // the referenced page
2218  $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
2219  }
2220  $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
2221  if (!$statement) {
2222  $message = 'SectionIndex: Query to fetch the content elements failed!';
2223  throw new \UnexpectedValueException($message, 1337334849);
2224  }
2225  $result = [];
2226  while ($row = $statement->fetch()) {
2227  $this->sys_page->versionOL('tt_content', $row);
2228  if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
2229  $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL);
2230  }
2231  if ($this->mconf['sectionIndex.']['type'] !== 'all') {
2232  $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
2233  $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
2234  $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
2235  if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
2236  continue;
2237  }
2238  }
2239  if (is_array($row)) {
2240  $uid = $row['uid'];
2241  $result[$uid] = $basePageRow;
2242  $result[$uid]['title'] = $row['header'];
2243  $result[$uid]['nav_title'] = $row['header'];
2244  // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
2245  $result[$uid]['nav_hide'] = 0;
2246  $result[$uid]['subtitle'] = $row['subheader'];
2247  $result[$uid]['starttime'] = $row['starttime'];
2248  $result[$uid]['endtime'] = $row['endtime'];
2249  $result[$uid]['fe_group'] = $row['fe_group'];
2250  $result[$uid]['media'] = $row['media'];
2251  $result[$uid]['header_layout'] = $row['header_layout'];
2252  $result[$uid]['bodytext'] = $row['bodytext'];
2253  $result[$uid]['image'] = $row['image'];
2254  $result[$uid]['sectionIndex_uid'] = $uid;
2255  }
2256  }
2257 
2258  return $result;
2259  }
2260 
2266  public function getSysPage()
2267  {
2268  return $this->sys_page;
2269  }
2270 
2276  public function getParentContentObject()
2277  {
2278  return $this->parent_cObj;
2279  }
2280 
2284  protected function getTypoScriptFrontendController()
2285  {
2286  return $GLOBALS['TSFE'];
2287  }
2288 
2292  protected function getTimeTracker()
2293  {
2294  return GeneralUtility::makeInstance(TimeTracker::class);
2295  }
2296 
2300  protected function getCache()
2301  {
2302  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2303  }
2304 
2308  protected function getRuntimeCache()
2309  {
2310  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
2311  }
2312 
2321  public function setParentMenu(array $menuArr = [], $menuItemKey)
2322  {
2323  // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
2324  if (is_array($menuArr)
2325  && (is_int($menuItemKey) && $menuItemKey >= 0 && isset($menuArr[$menuItemKey]))
2326  ) {
2327  $this->parentMenuArr = $menuArr;
2328  $this->parentMenuArrItemKey = $menuItemKey;
2329  }
2330  }
2331 
2337  protected function hasParentMenuArr()
2338  {
2339  return
2340  $this->menuNumber > 1
2341  && is_array($this->parentMenuArr)
2342  && !empty($this->parentMenuArr)
2343  ;
2344  }
2345 
2349  protected function hasParentMenuItemKey()
2350  {
2351  return null !== $this->parentMenuArrItemKey;
2352  }
2353 
2357  protected function hasParentMenuItem()
2358  {
2359  return
2360  $this->hasParentMenuArr()
2361  && $this->hasParentMenuItemKey()
2362  && isset($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2363  ;
2364  }
2365 
2371  public function getParentMenuArr()
2372  {
2373  return $this->hasParentMenuArr() ? $this->parentMenuArr : [];
2374  }
2375 
2381  public function getParentMenuItem()
2382  {
2383  // check if we have an parentMenuItem and if it is an array
2384  if ($this->hasParentMenuItem()
2385  && is_array($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2386  ) {
2388  }
2389 
2390  return null;
2391  }
2392 }
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static implodeAttributes(array $arr, $xhtmlSafe=false, $dontOmitBlankAttribs=false)
$languageItems
Definition: be_users.php:7
debug($variable='', $name=' *variable *', $line=' *line *', $file=' *file *', $recursiveDepth=3, $debugLevel='E_DEBUG')
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static hideIfDefaultLanguage($localizationConfiguration)
static makeInstance($className,... $constructorArguments)
static hideIfNotTranslated($l18n_cfg_fieldValue)
menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray='', $addParams='', $typeOverride='')
static explodeUrl2Array($string, $multidim=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix='')