TYPO3 CMS  TYPO3_7-6
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 = 0;
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 
237  public $parentMenuArr = [];
238 
245 
258  public function start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix = '')
259  {
260  $tsfe = $this->getTypoScriptFrontendController();
261  // Init:
262  $this->conf = $conf;
263  $this->menuNumber = $menuNumber;
264  $this->mconf = $conf[$this->menuNumber . $objSuffix . '.'];
265  $this->debug = $tsfe->debug;
266  $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
267  // In XHTML and HTML5 there is no "name" attribute anymore
268  switch ($tsfe->xhtmlDoctype) {
269  case 'xhtml_strict':
270  // intended fall-through
271  case 'xhtml_11':
272  // intended fall-through
273  case 'xhtml_2':
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 = $this->getDatabaseConnection()->cleanIntList($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  $databaseConnection = $this->getDatabaseConnection();
718  $menuItems = [];
719  if ($specialValue == '') {
720  $specialValue = $tsfe->page['uid'];
721  }
722  $items = GeneralUtility::intExplode(',', $specialValue);
723  foreach ($items as $id) {
724  $MP = $this->tmpl->getFromMPmap($id);
725  // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
726  $mount_info = $this->sys_page->getMountPointInfo($id);
727  if (is_array($mount_info)) {
728  if ($mount_info['overlay']) {
729  // Overlays should already have their full MPvars calculated:
730  $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
731  $MP = $MP ? $MP : $mount_info['MPvar'];
732  } else {
733  $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
734  }
735  $id = $mount_info['mount_pid'];
736  }
737  // Get sub-pages:
738  $res = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
739  while ($row = $databaseConnection->sql_fetch_assoc($res)) {
740  $tsfe->sys_page->versionOL('pages', $row, true);
741  if (!empty($row)) {
742  // Keep mount point?
743  $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
744  // There is a valid mount point.
745  if (is_array($mount_info) && $mount_info['overlay']) {
746  // Using "getPage" is OK since we need the check for enableFields
747  // AND for type 2 of mount pids we DO require a doktype < 200!
748  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
749  if (!empty($mp_row)) {
750  $row = $mp_row;
751  $row['_MP_PARAM'] = $mount_info['MPvar'];
752  } else {
753  // If the mount point could not be fetched with respect
754  // to enableFields, unset the row so it does not become a part of the menu!
755  unset($row);
756  }
757  }
758  // Add external MP params, then the row:
759  if (!empty($row)) {
760  if ($MP) {
761  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
762  }
763  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
764  }
765  }
766  }
767  $databaseConnection->sql_free_result($res);
768  }
769  return $menuItems;
770  }
771 
778  protected function prepareMenuItemsForListMenu($specialValue)
779  {
780  $menuItems = [];
781  if ($specialValue == '') {
782  $specialValue = $this->id;
783  }
784  $skippedEnableFields = [];
785  if (!empty($this->mconf['showAccessRestrictedPages'])) {
786  $skippedEnableFields = ['fe_group' => 1];
787  }
789  $loadDB = GeneralUtility::makeInstance(RelationHandler::class);
790  $loadDB->setFetchAllFields(true);
791  $loadDB->start($specialValue, 'pages');
792  $loadDB->additionalWhere['pages'] = $this->parent_cObj->enableFields('pages', false, $skippedEnableFields);
793  $loadDB->getFromDB();
794  foreach ($loadDB->itemArray as $val) {
795  $MP = $this->tmpl->getFromMPmap($val['id']);
796  // Keep mount point?
797  $mount_info = $this->sys_page->getMountPointInfo($val['id']);
798  // There is a valid mount point.
799  if (is_array($mount_info) && $mount_info['overlay']) {
800  // Using "getPage" is OK since we need the check for enableFields
801  // AND for type 2 of mount pids we DO require a doktype < 200!
802  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
803  if (!empty($mp_row)) {
804  $row = $mp_row;
805  $row['_MP_PARAM'] = $mount_info['MPvar'];
806  // Overlays should already have their full MPvars calculated
807  if ($mount_info['overlay']) {
808  $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
809  if ($MP) {
810  unset($row['_MP_PARAM']);
811  }
812  }
813  } else {
814  // If the mount point could not be fetched with respect to
815  // enableFields, unset the row so it does not become a part of the menu!
816  unset($row);
817  }
818  } else {
819  $row = $loadDB->results['pages'][$val['id']];
820  }
821  // Add versioning overlay for current page (to respect workspaces)
822  if (isset($row) && is_array($row)) {
823  $this->sys_page->versionOL('pages', $row, true);
824  }
825  // Add external MP params, then the row:
826  if (isset($row) && is_array($row)) {
827  if ($MP) {
828  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
829  }
830  $menuItems[] = $this->sys_page->getPageOverlay($row);
831  }
832  }
833  return $menuItems;
834  }
835 
843  protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
844  {
845  $tsfe = $this->getTypoScriptFrontendController();
846  $menuItems = [];
847  if ($specialValue == '') {
848  $specialValue = $tsfe->page['uid'];
849  }
850  $items = GeneralUtility::intExplode(',', $specialValue);
851  if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
852  $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
853  } else {
854  $depth = 20;
855  }
856  // Max number of items
857  $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
858  $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
859  if (!$limit) {
860  $limit = 10;
861  }
862  // *'auto', 'manual', 'tstamp'
863  $mode = $this->conf['special.']['mode'];
864  // Get id's
865  $id_list_arr = [];
866  foreach ($items as $id) {
867  $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
868  $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $bA, $bA - 1);
869  }
870  $id_list = implode(',', $id_list_arr);
871  // Get sortField (mode)
872  switch ($mode) {
873  case 'starttime':
874  $sortField = 'starttime';
875  break;
876  case 'lastUpdated':
877  case 'manual':
878  $sortField = 'lastUpdated';
879  break;
880  case 'tstamp':
881  $sortField = 'tstamp';
882  break;
883  case 'crdate':
884  $sortField = 'crdate';
885  break;
886  default:
887  $sortField = 'SYS_LASTCHANGED';
888  }
889  $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
890  if ($this->conf['special.']['excludeNoSearchPages']) {
891  $extraWhere .= ' AND pages.no_search=0';
892  }
893  if ($maxAge > 0) {
894  $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
895  }
896  $res = $this->parent_cObj->exec_getQuery('pages', [
897  'pidInList' => '0',
898  'uidInList' => $id_list,
899  'where' => $sortField . '>=0' . $extraWhere,
900  'orderBy' => $sortingField ?: $sortField . ' DESC',
901  'max' => $limit
902  ]);
903  while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($res)) {
904  $tsfe->sys_page->versionOL('pages', $row, true);
905  if (is_array($row)) {
906  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
907  }
908  }
909  $this->getDatabaseConnection()->sql_free_result($res);
910  return $menuItems;
911  }
912 
920  protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
921  {
922  $tsfe = $this->getTypoScriptFrontendController();
923  $menuItems = [];
924  list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
925  if (!$specialValue) {
926  $specialValue = $tsfe->page['uid'];
927  }
928  if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
929  $kw = isset($this->conf['special.']['setKeywords.']) ? $this->parent_cObj->stdWrap($this->conf['special.']['setKeywords'], $this->conf['special.']['setKeywords.']) : $this->conf['special.']['setKeywords'];
930  } else {
931  // The page record of the 'value'.
932  $value_rec = $this->sys_page->getPage($specialValue);
933  $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
934  // keywords.
935  $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
936  }
937  // *'auto', 'manual', 'tstamp'
938  $mode = $this->conf['special.']['mode'];
939  switch ($mode) {
940  case 'starttime':
941  $sortField = 'starttime';
942  break;
943  case 'lastUpdated':
944  case 'manual':
945  $sortField = 'lastUpdated';
946  break;
947  case 'tstamp':
948  $sortField = 'tstamp';
949  break;
950  case 'crdate':
951  $sortField = 'crdate';
952  break;
953  default:
954  $sortField = 'SYS_LASTCHANGED';
955  }
956  // Depth, limit, extra where
957  if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
958  $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
959  } else {
960  $depth = 20;
961  }
962  // Max number of items
963  $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
964  $extraWhere = ' AND pages.uid<>' . $specialValue . ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
965  if ($this->conf['special.']['excludeNoSearchPages']) {
966  $extraWhere .= ' AND pages.no_search=0';
967  }
968  // Start point
969  $eLevel = $this->parent_cObj->getKey(isset($this->conf['special.']['entryLevel.'])
970  ? $this->parent_cObj->stdWrap($this->conf['special.']['entryLevel'], $this->conf['special.']['entryLevel.'])
971  : $this->conf['special.']['entryLevel'], $this->tmpl->rootLine
972  );
973  $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
974  // Which field is for keywords
975  $kfield = 'keywords';
976  if ($this->conf['special.']['keywordsField']) {
977  list($kfield) = explode(' ', trim($this->conf['special.']['keywordsField']));
978  }
979  // If there are keywords and the startuid is present
980  if ($kw && $startUid) {
981  $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
982  $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
983  $kwArr = explode(',', $kw);
984  $keyWordsWhereArr = [];
985  foreach ($kwArr as $word) {
986  $word = trim($word);
987  if ($word) {
988  $keyWordsWhereArr[] = $kfield . ' LIKE \'%' . $this->getDatabaseConnection()->quoteStr($word, 'pages') . '%\'';
989  }
990  }
991  $where = empty($keyWordsWhereArr) ? '' : '(' . implode(' OR ', $keyWordsWhereArr) . ')';
992  $res = $this->parent_cObj->exec_getQuery('pages', [
993  'pidInList' => '0',
994  'uidInList' => $id_list,
995  'where' => $where . $extraWhere,
996  'orderBy' => $sortingField ?: $sortField . ' desc',
997  'max' => $limit
998  ]);
999  while (($row = $this->getDatabaseConnection()->sql_fetch_assoc($res))) {
1000  $tsfe->sys_page->versionOL('pages', $row, true);
1001  if (is_array($row)) {
1002  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
1003  }
1004  }
1005  $this->getDatabaseConnection()->sql_free_result($res);
1006  }
1007  return $menuItems;
1008  }
1009 
1015  protected function prepareMenuItemsForRootlineMenu()
1016  {
1017  $menuItems = [];
1018  $range = isset($this->conf['special.']['range.'])
1019  ? $this->parent_cObj->stdWrap($this->conf['special.']['range'], $this->conf['special.']['range.'])
1020  : $this->conf['special.']['range'];
1021  $begin_end = explode('|', $range);
1022  $begin_end[0] = (int)$begin_end[0];
1023  if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1024  $begin_end[1] = -1;
1025  }
1026  $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1027  $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1028  if ($endKey < $beginKey) {
1029  $endKey = $beginKey;
1030  }
1031  $rl_MParray = [];
1032  foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1033  // For overlaid mount points, set the variable right now:
1034  if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1035  $rl_MParray[] = $v_rl['_MP_PARAM'];
1036  }
1037  // Traverse rootline:
1038  if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1039  $temp_key = $k_rl;
1040  $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1041  if (!empty($menuItems[$temp_key])) {
1042  // If there are no specific target for the page, put the level specific target on.
1043  if (!$menuItems[$temp_key]['target']) {
1044  $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1045  $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1046  }
1047  } else {
1048  unset($menuItems[$temp_key]);
1049  }
1050  }
1051  // For normal mount points, set the variable for next level.
1052  if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1053  $rl_MParray[] = $v_rl['_MP_PARAM'];
1054  }
1055  }
1056  // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1057  if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1058  $menuItems = array_reverse($menuItems);
1059  }
1060  return $menuItems;
1061  }
1062 
1071  protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1072  {
1073  $menuItems = [];
1074  list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
1075  if (!$specialValue) {
1076  $specialValue = $this->getTypoScriptFrontendController()->page['uid'];
1077  }
1078  // Will not work out of rootline
1079  if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1080  $recArr = [];
1081  // The page record of the 'value'.
1082  $value_rec = $this->sys_page->getPage($specialValue);
1083  // 'up' page cannot be outside rootline
1084  if ($value_rec['pid']) {
1085  // The page record of 'up'.
1086  $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1087  }
1088  // If the 'up' item was NOT level 0 in rootline...
1089  if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1090  // The page record of "index".
1091  $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1092  }
1093  // check if certain pages should be excluded
1094  $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1095  if ($this->conf['special.']['excludeNoSearchPages']) {
1096  $additionalWhere .= ' AND pages.no_search=0';
1097  }
1098  // prev / next is found
1099  $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1100  $lastKey = 0;
1101  $nextActive = 0;
1102  foreach ($prevnext_menu as $k_b => $v_b) {
1103  if ($nextActive) {
1104  $recArr['next'] = $v_b;
1105  $nextActive = 0;
1106  }
1107  if ($v_b['uid'] == $specialValue) {
1108  if ($lastKey) {
1109  $recArr['prev'] = $prevnext_menu[$lastKey];
1110  }
1111  $nextActive = 1;
1112  }
1113  $lastKey = $k_b;
1114  }
1115 
1116  $recArr['first'] = reset($prevnext_menu);
1117  $recArr['last'] = end($prevnext_menu);
1118  // prevsection / nextsection is found
1119  // You can only do this, if there is a valid page two levels up!
1120  if (!empty($recArr['index']['uid'])) {
1121  $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1122  $lastKey = 0;
1123  $nextActive = 0;
1124  foreach ($prevnextsection_menu as $k_b => $v_b) {
1125  if ($nextActive) {
1126  $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1127  if (!empty($sectionRec_temp)) {
1128  $recArr['nextsection'] = reset($sectionRec_temp);
1129  $recArr['nextsection_last'] = end($sectionRec_temp);
1130  $nextActive = 0;
1131  }
1132  }
1133  if ($v_b['uid'] == $value_rec['pid']) {
1134  if ($lastKey) {
1135  $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1136  if (!empty($sectionRec_temp)) {
1137  $recArr['prevsection'] = reset($sectionRec_temp);
1138  $recArr['prevsection_last'] = end($sectionRec_temp);
1139  }
1140  }
1141  $nextActive = 1;
1142  }
1143  $lastKey = $k_b;
1144  }
1145  }
1146  if ($this->conf['special.']['items.']['prevnextToSection']) {
1147  if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1148  $recArr['prev'] = $recArr['prevsection_last'];
1149  }
1150  if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1151  $recArr['next'] = $recArr['nextsection'];
1152  }
1153  }
1154  $items = explode('|', $this->conf['special.']['items']);
1155  $c = 0;
1156  foreach ($items as $k_b => $v_b) {
1157  $v_b = strtolower(trim($v_b));
1158  if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1159  $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1160  }
1161  if (is_array($recArr[$v_b])) {
1162  $menuItems[$c] = $recArr[$v_b];
1163  if ($this->conf['special.'][$v_b . '.']['target']) {
1164  $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1165  }
1166  $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1167  if (is_array($tmpSpecialFields)) {
1168  foreach ($tmpSpecialFields as $fk => $val) {
1169  $menuItems[$c][$fk] = $val;
1170  }
1171  }
1172  $c++;
1173  }
1174  }
1175  }
1176  return $menuItems;
1177  }
1178 
1185  protected function analyzeCacheHashRequirements($queryString)
1186  {
1187  $parameters = GeneralUtility::explodeUrl2Array($queryString);
1188  if (!empty($parameters)) {
1189  if (!isset($parameters['id'])) {
1190  $queryString .= '&id=' . $this->getTypoScriptFrontendController()->id;
1191  }
1193  $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
1194  $cHashParameters = $cacheHashCalculator->getRelevantParameters($queryString);
1195  if (count($cHashParameters) > 1) {
1196  $this->useCacheHash = (
1197  $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ||
1198  !isset($parameters['no_cache']) ||
1199  !$parameters['no_cache']
1200  );
1201  }
1202  }
1203  }
1204 
1216  public function filterMenuPages(&$data, $banUidArray, $spacer)
1217  {
1218  $includePage = true;
1219  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'])) {
1220  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] as $classRef) {
1221  $hookObject = GeneralUtility::getUserObj($classRef);
1222  if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1223  throw new \UnexpectedValueException($classRef . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1224  }
1225  $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $spacer, $this);
1226  }
1227  }
1228  if (!$includePage) {
1229  return false;
1230  }
1231  if ($data['_SAFE']) {
1232  return true;
1233  }
1234 
1235  if (
1236  ($this->mconf['SPC'] || !$spacer) // If the spacer-function is not enabled, spacers will not enter the $menuArr
1237  && (!$data['nav_hide'] || $this->conf['includeNotInMenu']) // Not hidden in navigation
1238  && !GeneralUtility::inList($this->doktypeExcludeList, $data['doktype']) // Page may not be 'not_in_menu' or 'Backend User Section'
1239  && !ArrayUtility::inArray($banUidArray, $data['uid']) // not in banned uid's
1240  ) {
1241  // Checks if the default language version can be shown:
1242  // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
1243  $tsfe = $this->getTypoScriptFrontendController();
1244  $blockPage = GeneralUtility::hideIfDefaultLanguage($data['l18n_cfg']) && (!$tsfe->sys_language_uid || $tsfe->sys_language_uid && !$data['_PAGES_OVERLAY']);
1245  if (!$blockPage) {
1246  // Checking if a page should be shown in the menu depending on whether a translation exists:
1247  $tok = true;
1248  // There is an alternative language active AND the current page requires a translation:
1249  if ($tsfe->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1250  if (!$data['_PAGES_OVERLAY']) {
1251  $tok = false;
1252  }
1253  }
1254  // Continue if token is TRUE:
1255  if ($tok) {
1256  // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1257  if ($this->conf['protectLvar']) {
1258  $languageUid = (int)$tsfe->config['config']['sys_language_uid'];
1259  if ($languageUid && ($this->conf['protectLvar'] == 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
1260  $olRec = $tsfe->sys_page->getPageOverlay($data['uid'], $languageUid);
1261  if (empty($olRec)) {
1262  // If no pages_language_overlay record then page can NOT be accessed in
1263  // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1264  $data['_ADD_GETVARS'] .= '&L=0';
1265  }
1266  }
1267  }
1268  return true;
1269  }
1270  }
1271  }
1272  return false;
1273  }
1274 
1290  public function procesItemStates($splitCount)
1291  {
1292  // Prepare normal settings
1293  if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1294  // Setting a blank array if NO=1 and there are no properties.
1295  $this->mconf['NO.'] = [];
1296  }
1297  $NOconf = $this->tmpl->splitConfArray($this->mconf['NO.'], $splitCount);
1298  // Prepare rollOver settings, overriding normal settings
1299  $ROconf = [];
1300  if ($this->mconf['RO']) {
1301  $ROconf = $this->tmpl->splitConfArray($this->mconf['RO.'], $splitCount);
1302  }
1303  // Prepare IFSUB settings, overriding normal settings
1304  // IFSUB is TRUE if there exist submenu items to the current item
1305  if (!empty($this->mconf['IFSUB'])) {
1306  $IFSUBconf = null;
1307  $IFSUBROconf = null;
1308  foreach ($NOconf as $key => $val) {
1309  if ($this->isItemState('IFSUB', $key)) {
1310  // if this is the first IFSUB element, we must generate IFSUB.
1311  if ($IFSUBconf === null) {
1312  $IFSUBconf = $this->tmpl->splitConfArray($this->mconf['IFSUB.'], $splitCount);
1313  if (!empty($this->mconf['IFSUBRO'])) {
1314  $IFSUBROconf = $this->tmpl->splitConfArray($this->mconf['IFSUBRO.'], $splitCount);
1315  }
1316  }
1317  // Substitute normal with ifsub
1318  if (isset($IFSUBconf[$key])) {
1319  $NOconf[$key] = $IFSUBconf[$key];
1320  }
1321  // If rollOver on normal, we must apply a state for rollOver on the active
1322  if ($ROconf) {
1323  // If RollOver on active then apply this
1324  $ROconf[$key] = !empty($IFSUBROconf[$key]) ? $IFSUBROconf[$key] : $IFSUBconf[$key];
1325  }
1326  }
1327  }
1328  }
1329  // Prepare active settings, overriding normal settings
1330  if (!empty($this->mconf['ACT'])) {
1331  $ACTconf = null;
1332  $ACTROconf = null;
1333  // Find active
1334  foreach ($NOconf as $key => $val) {
1335  if ($this->isItemState('ACT', $key)) {
1336  // If this is the first 'active', we must generate ACT.
1337  if ($ACTconf === null) {
1338  $ACTconf = $this->tmpl->splitConfArray($this->mconf['ACT.'], $splitCount);
1339  // Prepare active rollOver settings, overriding normal active settings
1340  if (!empty($this->mconf['ACTRO'])) {
1341  $ACTROconf = $this->tmpl->splitConfArray($this->mconf['ACTRO.'], $splitCount);
1342  }
1343  }
1344  // Substitute normal with active
1345  if (isset($ACTconf[$key])) {
1346  $NOconf[$key] = $ACTconf[$key];
1347  }
1348  // If rollOver on normal, we must apply a state for rollOver on the active
1349  if ($ROconf) {
1350  // If RollOver on active then apply this
1351  $ROconf[$key] = !empty($ACTROconf[$key]) ? $ACTROconf[$key] : $ACTconf[$key];
1352  }
1353  }
1354  }
1355  }
1356  // Prepare ACT (active)/IFSUB settings, overriding normal settings
1357  // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
1358  if (!empty($this->mconf['ACTIFSUB'])) {
1359  $ACTIFSUBconf = null;
1360  $ACTIFSUBROconf = null;
1361  // Find active
1362  foreach ($NOconf as $key => $val) {
1363  if ($this->isItemState('ACTIFSUB', $key)) {
1364  // If this is the first 'active', we must generate ACTIFSUB.
1365  if ($ACTIFSUBconf === null) {
1366  $ACTIFSUBconf = $this->tmpl->splitConfArray($this->mconf['ACTIFSUB.'], $splitCount);
1367  // Prepare active rollOver settings, overriding normal active settings
1368  if (!empty($this->mconf['ACTIFSUBRO'])) {
1369  $ACTIFSUBROconf = $this->tmpl->splitConfArray($this->mconf['ACTIFSUBRO.'], $splitCount);
1370  }
1371  }
1372  // Substitute normal with active
1373  if (isset($ACTIFSUBconf[$key])) {
1374  $NOconf[$key] = $ACTIFSUBconf[$key];
1375  }
1376  // If rollOver on normal, we must apply a state for rollOver on the active
1377  if ($ROconf) {
1378  // If RollOver on active then apply this
1379  $ROconf[$key] = !empty($ACTIFSUBROconf[$key]) ? $ACTIFSUBROconf[$key] : $ACTIFSUBconf[$key];
1380  }
1381  }
1382  }
1383  }
1384  // Prepare CUR (current) settings, overriding normal settings
1385  // CUR is TRUE if the current page equals the item here!
1386  if (!empty($this->mconf['CUR'])) {
1387  $CURconf = null;
1388  $CURROconf = null;
1389  foreach ($NOconf as $key => $val) {
1390  if ($this->isItemState('CUR', $key)) {
1391  // if this is the first 'current', we must generate CUR. Basically this control is just inherited
1392  // from the other implementations as current would only exist one time and that's it
1393  // (unless you use special-features of HMENU)
1394  if ($CURconf === null) {
1395  $CURconf = $this->tmpl->splitConfArray($this->mconf['CUR.'], $splitCount);
1396  if (!empty($this->mconf['CURRO'])) {
1397  $CURROconf = $this->tmpl->splitConfArray($this->mconf['CURRO.'], $splitCount);
1398  }
1399  }
1400  // Substitute normal with current
1401  if (isset($CURconf[$key])) {
1402  $NOconf[$key] = $CURconf[$key];
1403  }
1404  // If rollOver on normal, we must apply a state for rollOver on the active
1405  if ($ROconf) {
1406  // If RollOver on active then apply this
1407  $ROconf[$key] = !empty($CURROconf[$key]) ? $CURROconf[$key] : $CURconf[$key];
1408  }
1409  }
1410  }
1411  }
1412  // Prepare CUR (current)/IFSUB settings, overriding normal settings
1413  // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
1414  if (!empty($this->mconf['CURIFSUB'])) {
1415  $CURIFSUBconf = null;
1416  $CURIFSUBROconf = null;
1417  foreach ($NOconf as $key => $val) {
1418  if ($this->isItemState('CURIFSUB', $key)) {
1419  // If this is the first 'current', we must generate CURIFSUB.
1420  if ($CURIFSUBconf === null) {
1421  $CURIFSUBconf = $this->tmpl->splitConfArray($this->mconf['CURIFSUB.'], $splitCount);
1422  // Prepare current rollOver settings, overriding normal current settings
1423  if (!empty($this->mconf['CURIFSUBRO'])) {
1424  $CURIFSUBROconf = $this->tmpl->splitConfArray($this->mconf['CURIFSUBRO.'], $splitCount);
1425  }
1426  }
1427  // Substitute normal with active
1428  if ($CURIFSUBconf[$key]) {
1429  $NOconf[$key] = $CURIFSUBconf[$key];
1430  }
1431  // If rollOver on normal, we must apply a state for rollOver on the current
1432  if ($ROconf) {
1433  // If RollOver on current then apply this
1434  $ROconf[$key] = !empty($CURIFSUBROconf[$key]) ? $CURIFSUBROconf[$key] : $CURIFSUBconf[$key];
1435  }
1436  }
1437  }
1438  }
1439  // Prepare active settings, overriding normal settings
1440  if (!empty($this->mconf['USR'])) {
1441  $USRconf = null;
1442  $USRROconf = null;
1443  // Find active
1444  foreach ($NOconf as $key => $val) {
1445  if ($this->isItemState('USR', $key)) {
1446  // if this is the first active, we must generate USR.
1447  if ($USRconf === null) {
1448  $USRconf = $this->tmpl->splitConfArray($this->mconf['USR.'], $splitCount);
1449  // Prepare active rollOver settings, overriding normal active settings
1450  if (!empty($this->mconf['USRRO'])) {
1451  $USRROconf = $this->tmpl->splitConfArray($this->mconf['USRRO.'], $splitCount);
1452  }
1453  }
1454  // Substitute normal with active
1455  if ($USRconf[$key]) {
1456  $NOconf[$key] = $USRconf[$key];
1457  }
1458  // If rollOver on normal, we must apply a state for rollOver on the active
1459  if ($ROconf) {
1460  // If RollOver on active then apply this
1461  $ROconf[$key] = !empty($USRROconf[$key]) ? $USRROconf[$key] : $USRconf[$key];
1462  }
1463  }
1464  }
1465  }
1466  // Prepare spacer settings, overriding normal settings
1467  if (!empty($this->mconf['SPC'])) {
1468  $SPCconf = null;
1469  // Find spacers
1470  foreach ($NOconf as $key => $val) {
1471  if ($this->isItemState('SPC', $key)) {
1472  // If this is the first spacer, we must generate SPC.
1473  if ($SPCconf === null) {
1474  $SPCconf = $this->tmpl->splitConfArray($this->mconf['SPC.'], $splitCount);
1475  }
1476  // Substitute normal with spacer
1477  if (isset($SPCconf[$key])) {
1478  $NOconf[$key] = $SPCconf[$key];
1479  }
1480  }
1481  }
1482  }
1483  // Prepare Userdefined settings
1484  if (!empty($this->mconf['USERDEF1'])) {
1485  $USERDEF1conf = null;
1486  $USERDEF1ROconf = null;
1487  // Find active
1488  foreach ($NOconf as $key => $val) {
1489  if ($this->isItemState('USERDEF1', $key)) {
1490  // If this is the first active, we must generate USERDEF1.
1491  if ($USERDEF1conf === null) {
1492  $USERDEF1conf = $this->tmpl->splitConfArray($this->mconf['USERDEF1.'], $splitCount);
1493  // Prepare active rollOver settings, overriding normal active settings
1494  if (!empty($this->mconf['USERDEF1RO'])) {
1495  $USERDEF1ROconf = $this->tmpl->splitConfArray($this->mconf['USERDEF1RO.'], $splitCount);
1496  }
1497  }
1498  // Substitute normal with active
1499  if (isset($USERDEF1conf[$key])) {
1500  $NOconf[$key] = $USERDEF1conf[$key];
1501  }
1502  // If rollOver on normal, we must apply a state for rollOver on the active
1503  if ($ROconf) {
1504  // If RollOver on active then apply this
1505  $ROconf[$key] = !empty($USERDEF1ROconf[$key]) ? $USERDEF1ROconf[$key] : $USERDEF1conf[$key];
1506  }
1507  }
1508  }
1509  }
1510  // Prepare Userdefined settings
1511  if (!empty($this->mconf['USERDEF2'])) {
1512  $USERDEF2conf = null;
1513  $USERDEF2ROconf = null;
1514  // Find active
1515  foreach ($NOconf as $key => $val) {
1516  if ($this->isItemState('USERDEF2', $key)) {
1517  // If this is the first active, we must generate USERDEF2.
1518  if ($USERDEF2conf === null) {
1519  $USERDEF2conf = $this->tmpl->splitConfArray($this->mconf['USERDEF2.'], $splitCount);
1520  // Prepare active rollOver settings, overriding normal active settings
1521  if (!empty($this->mconf['USERDEF2RO'])) {
1522  $USERDEF2ROconf = $this->tmpl->splitConfArray($this->mconf['USERDEF2RO.'], $splitCount);
1523  }
1524  }
1525  // Substitute normal with active
1526  if (isset($USERDEF2conf[$key])) {
1527  $NOconf[$key] = $USERDEF2conf[$key];
1528  }
1529  // If rollOver on normal, we must apply a state for rollOver on the active
1530  if ($ROconf) {
1531  // If RollOver on active then apply this
1532  $ROconf[$key] = !empty($USERDEF2ROconf[$key]) ? $USERDEF2ROconf[$key] : $USERDEF2conf[$key];
1533  }
1534  }
1535  }
1536  }
1537  return [$NOconf, $ROconf];
1538  }
1539 
1550  public function link($key, $altTarget = '', $typeOverride = '')
1551  {
1552  $runtimeCache = $this->getRuntimeCache();
1553  $MP_var = $this->getMPvar($key);
1554  $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . $MP_var . serialize($this->menuArr[$key]));
1555  $runtimeCachedLink = $runtimeCache->get($cacheId);
1556  if ($runtimeCachedLink !== false) {
1557  return $runtimeCachedLink;
1558  }
1559 
1560  // Mount points:
1561  $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1562  // Setting override ID
1563  if ($this->mconf['overrideId'] || $this->menuArr[$key]['overrideId']) {
1564  $overrideArray = [];
1565  // If a user script returned the value overrideId in the menu array we use that as page id
1566  $overrideArray['uid'] = $this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId'];
1567  $overrideArray['alias'] = '';
1568  // Clear MP parameters since ID was changed.
1569  $MP_params = '';
1570  } else {
1571  $overrideArray = '';
1572  }
1573  // Setting main target:
1574  if ($altTarget) {
1575  $mainTarget = $altTarget;
1576  } elseif ($this->mconf['target.']) {
1577  $mainTarget = $this->parent_cObj->stdWrap($this->mconf['target'], $this->mconf['target.']);
1578  } else {
1579  $mainTarget = $this->mconf['target'];
1580  }
1581  // Creating link:
1582  $addParams = $this->mconf['addParams'] . $MP_params;
1583  if ($this->mconf['collapse'] && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1584  $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1585  $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1586  $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1587  } else {
1588  $addParams .= $this->I['val']['additionalParams'] . $this->menuArr[$key]['_ADD_GETVARS'];
1589  $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1590  }
1591  // Override URL if using "External URL"
1592  if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_LINK) {
1593  if ($this->menuArr[$key]['urltype'] == 3 && GeneralUtility::validEmail($this->menuArr[$key]['url'])) {
1594  // Create mailto-link using \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink (concerning spamProtectEmailAddresses):
1595  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $this->menuArr[$key]['url']]);
1596  $LD['target'] = '';
1597  } else {
1598  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $this->getSysPage()->getExtURL($this->menuArr[$key])]);
1599  }
1600  }
1601 
1602  $tsfe = $this->getTypoScriptFrontendController();
1603 
1604  // Override url if current page is a shortcut
1605  $shortcut = null;
1606  if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_SHORTCUT && $this->menuArr[$key]['shortcut_mode'] != PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1607  $menuItem = $this->determineOriginalShortcutPage($this->menuArr[$key]);
1608  try {
1609  $shortcut = $tsfe->getPageShortcut(
1610  $menuItem['shortcut'],
1611  $menuItem['shortcut_mode'],
1612  $menuItem['uid'],
1613  20,
1614  [],
1615  true
1616  );
1617  } catch (\Exception $ex) {
1618  }
1619  if (!is_array($shortcut)) {
1620  $runtimeCache->set($cacheId, []);
1621  return [];
1622  }
1623  // Only setting url, not target
1624  $LD['totalURL'] = $this->parent_cObj->typoLink_URL([
1625  'parameter' => $shortcut['uid'],
1626  'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1627  'linkAccessRestrictedPages' => !empty($this->mconf['showAccessRestrictedPages'])
1628  ]);
1629  }
1630  if ($shortcut) {
1631  $pageData = $shortcut;
1632  $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1633  } else {
1634  $pageData = $this->menuArr[$key];
1635  }
1636  // Manipulation in case of access restricted pages:
1637  $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1638  // Overriding URL / Target if set to do so:
1639  if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
1640  $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1641  if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1642  $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1643  }
1644  }
1645  // OnClick open in windows.
1646  $onClick = '';
1647  if ($this->mconf['JSWindow']) {
1648  $conf = $this->mconf['JSWindow.'];
1649  $url = $LD['totalURL'];
1650  $LD['totalURL'] = '#';
1651  $onClick = 'openPic('
1652  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1653  . '\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1654  . GeneralUtility::quoteJSvalue($conf['params']) . '); return false;';
1655  $tsfe->setJS('openPic');
1656  }
1657  // look for type and popup
1658  // following settings are valid in field target:
1659  // 230 will add type=230 to the link
1660  // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1661  // 230 _blank will add type=230 to the link and open with target "_blank"
1662  // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1663  $matches = [];
1664  $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1665  if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) || $targetIsType) {
1666  // has type?
1667  if ((int)$matches[1] || $targetIsType) {
1668  $LD['totalURL'] = $this->parent_cObj->URLqMark($LD['totalURL'], '&type=' . ($targetIsType ?: (int)$matches[1]));
1669  $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1670  }
1671  // Open in popup window?
1672  if ($matches[3] && $matches[4]) {
1673  $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1674  $onClick = 'vHWin=window.open('
1675  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1676  . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1677  $LD['target'] = '';
1678  }
1679  }
1680  // out:
1681  $list = [];
1682  // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1683  // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1684  // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1685  $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1686  $list['TARGET'] = $LD['target'];
1687  $list['onClick'] = $onClick;
1688  $runtimeCache->set($cacheId, $list);
1689  return $list;
1690  }
1691 
1703  protected function determineOriginalShortcutPage(array $page)
1704  {
1705  // Check if modification is required
1706  if (
1707  $this->getTypoScriptFrontendController()->sys_language_uid > 0
1708  && empty($page['shortcut'])
1709  && !empty($page['uid'])
1710  && !empty($page['_PAGES_OVERLAY'])
1711  && !empty($page['_PAGES_OVERLAY_UID'])
1712  ) {
1713  // Using raw record since the record was overlaid and is correct already:
1714  $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1715 
1716  if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1717  $page['shortcut'] = $originalPage['shortcut'];
1718  }
1719  }
1720 
1721  return $page;
1722  }
1723 
1733  public function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1734  {
1735  // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1736  if ($this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1737  $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1738  $addParams = str_replace(
1739  [
1740  '###RETURN_URL###',
1741  '###PAGE_ID###'
1742  ],
1743  [
1744  rawurlencode($LD['totalURL']),
1745  isset($page['_SHORTCUT_PAGE_UID']) ? $page['_SHORTCUT_PAGE_UID'] : $page['uid']
1746  ],
1747  $this->mconf['showAccessRestrictedPages.']['addParams']
1748  );
1749  $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', '', $addParams, $typeOverride);
1750  }
1751  }
1752 
1761  public function subMenu($uid, $objSuffix = '')
1762  {
1763  // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1764  $altArray = '';
1765  if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU']) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1766  $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1767  }
1768  // Make submenu if the page is the next active
1769  $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix];
1770  // stdWrap for expAll
1771  if (isset($this->mconf['expAll.'])) {
1772  $this->mconf['expAll'] = $this->parent_cObj->stdWrap($this->mconf['expAll'], $this->mconf['expAll.']);
1773  }
1774  if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !$this->mconf['sectionIndex']) {
1775  try {
1776  $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1778  $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1779  $submenu->entryLevel = $this->entryLevel + 1;
1780  $submenu->rL_uidRegister = $this->rL_uidRegister;
1781  $submenu->MP_array = $this->MP_array;
1782  if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1783  $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1784  }
1785  // Especially scripts that build the submenu needs the parent data
1786  $submenu->parent_cObj = $this->parent_cObj;
1787  $submenu->setParentMenu($this->menuArr, $this->I['key']);
1788  // Setting alternativeMenuTempArray (will be effective only if an array)
1789  if (is_array($altArray)) {
1790  $submenu->alternativeMenuTempArray = $altArray;
1791  }
1792  if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1793  $submenu->makeMenu();
1794  // Memorize the current menu item count
1795  $tsfe = $this->getTypoScriptFrontendController();
1796  $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1797  // Reset the menu item count for the submenu
1798  $tsfe->register['count_MENUOBJ'] = 0;
1799  $content = $submenu->writeMenu();
1800  // Restore the item count now that the submenu has been handled
1801  $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1802  $tsfe->register['count_menuItems'] = count($this->menuArr);
1803  return $content;
1804  }
1805  } catch (Exception\NoSuchMenuTypeException $e) {
1806  }
1807  }
1808  return '';
1809  }
1810 
1820  public function isNext($uid, $MPvar = '')
1821  {
1822  // Check for always active PIDs:
1823  if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1824  return true;
1825  }
1826  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1827  if ($uid && $testUid == $this->nextActive) {
1828  return true;
1829  }
1830  return false;
1831  }
1832 
1841  public function isActive($uid, $MPvar = '')
1842  {
1843  // Check for always active PIDs:
1844  if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1845  return true;
1846  }
1847  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1848  if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1849  return true;
1850  }
1851  return false;
1852  }
1853 
1862  public function isCurrent($uid, $MPvar = '')
1863  {
1864  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1865  return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1866  }
1867 
1876  public function isSubMenu($uid)
1877  {
1878  $cacheId = 'menucontentobject-is-submenu-decision-' . $uid;
1879  $runtimeCache = $this->getRuntimeCache();
1880  $cachedDecision = $runtimeCache->get($cacheId);
1881  if (isset($cachedDecision['result'])) {
1882  return $cachedDecision['result'];
1883  }
1884  // Looking for a mount-pid for this UID since if that
1885  // exists we should look for a subpages THERE and not in the input $uid;
1886  $mount_info = $this->sys_page->getMountPointInfo($uid);
1887  if (is_array($mount_info)) {
1888  $uid = $mount_info['mount_pid'];
1889  }
1890  $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1891  $hasSubPages = false;
1892  $bannedUids = $this->getBannedUids();
1893  foreach ($recs as $theRec) {
1894  // no valid subpage if the document type is excluded from the menu
1895  if (GeneralUtility::inList($this->doktypeExcludeList, $theRec['doktype'])) {
1896  continue;
1897  }
1898  // No valid subpage if the page is hidden inside menus and
1899  // it wasn't forced to show such entries
1900  if ($theRec['nav_hide'] && !$this->conf['includeNotInMenu']) {
1901  continue;
1902  }
1903  // No valid subpage if the default language should be shown and the page settings
1904  // are excluding the visibility of the default language
1905  if (!$this->getTypoScriptFrontendController()->sys_language_uid && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
1906  continue;
1907  }
1908  // No valid subpage if the alternative language should be shown and the page settings
1909  // are requiring a valid overlay but it doesn't exists
1910  $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg']);
1911  if ($this->getTypoScriptFrontendController()->sys_language_uid && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1912  continue;
1913  }
1914  // No valid subpage if the subpage is banned by excludeUidList
1915  if (in_array($theRec['uid'], $bannedUids)) {
1916  continue;
1917  }
1918  $hasSubPages = true;
1919  break;
1920  }
1921  $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1922  return $hasSubPages;
1923  }
1924 
1934  public function isItemState($kind, $key)
1935  {
1936  $natVal = false;
1937  // If any value is set for ITEM_STATE the normal evaluation is discarded
1938  if ($this->menuArr[$key]['ITEM_STATE']) {
1939  if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1940  $natVal = true;
1941  }
1942  } else {
1943  switch ($kind) {
1944  case 'SPC':
1945  $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1946  break;
1947  case 'IFSUB':
1948  $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1949  break;
1950  case 'ACT':
1951  $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1952  break;
1953  case 'ACTIFSUB':
1954  $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1955  break;
1956  case 'CUR':
1957  $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
1958  break;
1959  case 'CURIFSUB':
1960  $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1961  break;
1962  case 'USR':
1963  $natVal = (bool)$this->menuArr[$key]['fe_group'];
1964  break;
1965  }
1966  }
1967  return $natVal;
1968  }
1969 
1977  public function accessKey($title)
1978  {
1979  $tsfe = $this->getTypoScriptFrontendController();
1980  // The global array ACCESSKEY is used to globally control if letters are already used!!
1981  $result = [];
1982  $title = trim(strip_tags($title));
1983  $titleLen = strlen($title);
1984  for ($a = 0; $a < $titleLen; $a++) {
1985  $key = strtoupper(substr($title, $a, 1));
1986  if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
1987  $tsfe->accessKey[$key] = 1;
1988  $result['code'] = ' accesskey="' . $key . '"';
1989  $result['alt'] = ' (ALT+' . $key . ')';
1990  $result['key'] = $key;
1991  break;
1992  }
1993  }
1994  return $result;
1995  }
1996 
2006  public function userProcess($mConfKey, $passVar)
2007  {
2008  if ($this->mconf[$mConfKey]) {
2009  $funcConf = $this->mconf[$mConfKey . '.'];
2010  $funcConf['parentObj'] = $this;
2011  $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
2012  }
2013  return $passVar;
2014  }
2015 
2022  public function setATagParts()
2023  {
2024  $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
2025  $params = $params !== '' ? ' ' . $params : '';
2026  $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], 1) . $params . '>';
2027  $this->I['A2'] = '</a>';
2028  }
2029 
2038  public function getPageTitle($title, $nav_title)
2039  {
2040  return trim($nav_title) !== '' ? $nav_title : $title;
2041  }
2042 
2050  public function getMPvar($key)
2051  {
2052  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
2053  $localMP_array = $this->MP_array;
2054  // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
2055  if ($this->menuArr[$key]['_MP_PARAM']) {
2056  $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
2057  }
2058  return !empty($localMP_array) ? implode(',', $localMP_array) : '';
2059  }
2060  return '';
2061  }
2062 
2069  public function getDoktypeExcludeWhere()
2070  {
2071  return $this->doktypeExcludeList ? ' AND pages.doktype NOT IN (' . $this->doktypeExcludeList . ')' : '';
2072  }
2073 
2080  public function getBannedUids()
2081  {
2082  $excludeUidList = isset($this->conf['excludeUidList.'])
2083  ? $this->parent_cObj->stdWrap($this->conf['excludeUidList'], $this->conf['excludeUidList.'])
2084  : $this->conf['excludeUidList'];
2085 
2086  if (!trim($excludeUidList)) {
2087  return [];
2088  }
2089 
2090  $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page['uid'], $excludeUidList);
2091  return GeneralUtility::intExplode(',', $banUidList);
2092  }
2093 
2106  public function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '')
2107  {
2108  $conf = [
2109  'parameter' => is_array($overrideArray) && $overrideArray['uid'] ? $overrideArray['uid'] : $page['uid']
2110  ];
2111  if (MathUtility::canBeInterpretedAsInteger($typeOverride)) {
2112  $conf['parameter'] .= ',' . (int)$typeOverride;
2113  }
2114  if ($addParams) {
2115  $conf['additionalParams'] = $addParams;
2116  }
2117  if ($no_cache) {
2118  $conf['no_cache'] = true;
2119  } elseif ($this->useCacheHash) {
2120  $conf['useCacheHash'] = true;
2121  }
2122  if ($oTarget) {
2123  $conf['target'] = $oTarget;
2124  }
2125  if ($page['sectionIndex_uid']) {
2126  $conf['section'] = $page['sectionIndex_uid'];
2127  }
2128  $conf['linkAccessRestrictedPages'] = !empty($this->mconf['showAccessRestrictedPages']);
2129  $this->parent_cObj->typoLink('|', $conf);
2130  $LD = $this->parent_cObj->lastTypoLinkLD;
2131  $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
2132  return $LD;
2133  }
2134 
2146  protected function sectionIndex($altSortField, $pid = null)
2147  {
2148  $pid = (int)($pid ?: $this->id);
2149  $basePageRow = $this->sys_page->getPage($pid);
2150  if (!is_array($basePageRow)) {
2151  return [];
2152  }
2153  $tsfe = $this->getTypoScriptFrontendController();
2154  $configuration = $this->mconf['sectionIndex.'];
2155  $useColPos = 0;
2156  if (trim($configuration['useColPos']) !== '' || is_array($configuration['useColPos.'])) {
2157  $useColPos = $tsfe->cObj->stdWrap($configuration['useColPos'], $configuration['useColPos.']);
2158  $useColPos = (int)$useColPos;
2159  }
2160  $selectSetup = [
2161  'pidInList' => $pid,
2162  'orderBy' => $altSortField,
2163  'languageField' => 'sys_language_uid',
2164  'where' => $useColPos >= 0 ? 'colPos=' . $useColPos : ''
2165  ];
2166  if ($basePageRow['content_from_pid']) {
2167  // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
2168  // the referenced page
2169  $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
2170  }
2171  $resource = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
2172  if (!$resource) {
2173  $message = 'SectionIndex: Query to fetch the content elements failed!';
2174  throw new \UnexpectedValueException($message, 1337334849);
2175  }
2176  $result = [];
2177  while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($resource)) {
2178  $this->sys_page->versionOL('tt_content', $row);
2179  if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
2180  $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL);
2181  }
2182  if ($this->mconf['sectionIndex.']['type'] !== 'all') {
2183  $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
2184  $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
2185  $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
2186  if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
2187  continue;
2188  }
2189  }
2190  if (is_array($row)) {
2191  $uid = $row['uid'];
2192  $result[$uid] = $basePageRow;
2193  $result[$uid]['title'] = $row['header'];
2194  $result[$uid]['nav_title'] = $row['header'];
2195  // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
2196  $result[$uid]['nav_hide'] = 0;
2197  $result[$uid]['subtitle'] = $row['subheader'];
2198  $result[$uid]['starttime'] = $row['starttime'];
2199  $result[$uid]['endtime'] = $row['endtime'];
2200  $result[$uid]['fe_group'] = $row['fe_group'];
2201  $result[$uid]['media'] = $row['media'];
2202  $result[$uid]['header_layout'] = $row['header_layout'];
2203  $result[$uid]['bodytext'] = $row['bodytext'];
2204  $result[$uid]['image'] = $row['image'];
2205  $result[$uid]['sectionIndex_uid'] = $uid;
2206  }
2207  }
2208  $this->getDatabaseConnection()->sql_free_result($resource);
2209  return $result;
2210  }
2211 
2217  public function getSysPage()
2218  {
2219  return $this->sys_page;
2220  }
2221 
2227  public function getParentContentObject()
2228  {
2229  return $this->parent_cObj;
2230  }
2231 
2235  protected function getDatabaseConnection()
2236  {
2237  return $GLOBALS['TYPO3_DB'];
2238  }
2239 
2243  protected function getTypoScriptFrontendController()
2244  {
2245  return $GLOBALS['TSFE'];
2246  }
2247 
2251  protected function getTimeTracker()
2252  {
2253  return $GLOBALS['TT'];
2254  }
2255 
2259  protected function getCache()
2260  {
2261  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2262  }
2263 
2267  protected function getRuntimeCache()
2268  {
2269  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
2270  }
2271 
2281  public function setParentMenu(array $menuArr = [], $menuItemKey)
2282  {
2283  // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
2284  if (is_array($menuArr)
2285  && (is_int($menuItemKey) && $menuItemKey >= 0 && isset($menuArr[$menuItemKey]))
2286  ) {
2287  $this->parentMenuArr = $menuArr;
2288  $this->parentMenuArrItemKey = $menuItemKey;
2289  }
2290  }
2291 
2297  protected function hasParentMenuArr()
2298  {
2299  return
2300  $this->menuNumber > 1
2301  && is_array($this->parentMenuArr)
2302  && !empty($this->parentMenuArr)
2303  ;
2304  }
2305 
2309  protected function hasParentMenuItemKey()
2310  {
2311  return null !== $this->parentMenuArrItemKey;
2312  }
2313 
2317  protected function hasParentMenuItem()
2318  {
2319  return
2320  $this->hasParentMenuArr()
2321  && $this->hasParentMenuItemKey()
2322  && isset($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2323  ;
2324  }
2325 
2331  public function getParentMenuArr()
2332  {
2333  return $this->hasParentMenuArr() ? $this->parentMenuArr : [];
2334  }
2335 
2341  public function getParentMenuItem()
2342  {
2343  // check if we have an parentMenuItem and if it is an array
2344  if ($this->hasParentMenuItem()
2345  && is_array($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2346  ) {
2348  }
2349 
2350  return null;
2351  }
2352 }
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
if(!defined("DB_ERROR")) define("DB_ERROR"
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 hideIfDefaultLanguage($localizationConfiguration)
static hideIfNotTranslated($l18n_cfg_fieldValue)
static inArray(array $in_array, $item)
menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray='', $addParams='', $typeOverride='')
static explodeUrl2Array($string, $multidim=false)
$uid
Definition: server.php:38
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='')