TYPO3  7.6
AbstractMenuContentObject.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Frontend\ContentObject\Menu;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
29 
38 {
44  public $menuNumber = 1;
45 
51  public $entryLevel = 0;
52 
58  public $spacerIDList = '199';
59 
65  public $doktypeExcludeList = '6';
66 
70  public $alwaysActivePIDlist = array();
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 = array();
105 
111  public $conf = array();
112 
118  public $mconf = array();
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 = array();
161 
168  public $rL_uidRegister = '';
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 
244  public function start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix = '')
245  {
246  $tsfe = $this->getTypoScriptFrontendController();
247  // Init:
248  $this->conf = $conf;
249  $this->menuNumber = $menuNumber;
250  $this->mconf = $conf[$this->menuNumber . $objSuffix . '.'];
251  $this->debug = $tsfe->debug;
252  $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
253  // In XHTML there is no "name" attribute anymore
254  switch ($tsfe->xhtmlDoctype) {
255  case 'xhtml_strict':
256  // intended fall-through
257  case 'xhtml_11':
258  // intended fall-through
259  case 'xhtml_2':
260  // intended fall-through
261  case 'html5':
262  $this->nameAttribute = 'id';
263  break;
264  default:
265  $this->nameAttribute = 'name';
266  }
267  // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the sys_page object
268  if ($this->conf[$this->menuNumber . $objSuffix] && is_object($tmpl) && is_object($sys_page)) {
269  $this->tmpl = $tmpl;
270  $this->sys_page = $sys_page;
271  // alwaysActivePIDlist initialized:
272  if (trim($this->conf['alwaysActivePIDlist']) || isset($this->conf['alwaysActivePIDlist.'])) {
273  if (isset($this->conf['alwaysActivePIDlist.'])) {
274  $this->conf['alwaysActivePIDlist'] = $this->parent_cObj->stdWrap(
275  $this->conf['alwaysActivePIDlist'],
276  $this->conf['alwaysActivePIDlist.']
277  );
278  }
279  $this->alwaysActivePIDlist = GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
280  }
281  // 'not in menu' doktypes
282  if ($this->conf['excludeDoktypes']) {
283  $this->doktypeExcludeList = $this->getDatabaseConnection()->cleanIntList($this->conf['excludeDoktypes']);
284  }
285  // EntryLevel
286  $this->entryLevel = $this->parent_cObj->getKey(
287  isset($conf['entryLevel.']) ? $this->parent_cObj->stdWrap(
288  $conf['entryLevel'],
289  $conf['entryLevel.']
290  ) : $conf['entryLevel'],
291  $this->tmpl->rootLine
292  );
293  // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
294  // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
295  if ($id) {
296  $this->id = (int)$id;
297  } else {
298  // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
299  $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
300  // Traverse rootline to build MP_array of pages BEFORE the entryLevel
301  // (MP var for ->id is picked up in the next part of the code...)
302  foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
303  // For overlaid mount points, set the variable right now:
304  if ($levelRec['_MP_PARAM'] && $levelRec['_MOUNT_OL']) {
305  $this->MP_array[] = $levelRec['_MP_PARAM'];
306  }
307  // Break when entry level is reached:
308  if ($entryLevel >= $this->entryLevel) {
309  break;
310  }
311  // For normal mount points, set the variable for next level.
312  if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
313  $this->MP_array[] = $levelRec['_MP_PARAM'];
314  }
315  }
316  }
317  // Return FALSE if no page ID was set (thus no menu of subpages can be made).
318  if ($this->id <= 0) {
319  return false;
320  }
321  // Check if page is a mount point, and if so set id and MP_array
322  // (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...)
323  $mount_info = $this->sys_page->getMountPointInfo($this->id);
324  if (is_array($mount_info)) {
325  $this->MP_array[] = $mount_info['MPvar'];
326  $this->id = $mount_info['mount_pid'];
327  }
328  // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
329  // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
330  if (!is_array($this->rL_uidRegister)) {
331  $rl_MParray = array();
332  foreach ($this->tmpl->rootLine as $v_rl) {
333  // For overlaid mount points, set the variable right now:
334  if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
335  $rl_MParray[] = $v_rl['_MP_PARAM'];
336  }
337  // Add to register:
338  $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
339  (!empty($rl_MParray)
340  ? ':' . implode(',', $rl_MParray)
341  : ''
342  );
343  // For normal mount points, set the variable for next level.
344  if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
345  $rl_MParray[] = $v_rl['_MP_PARAM'];
346  }
347  }
348  }
349  // Set $directoryLevel so the following evalution of the nextActive will not return
350  // an invalid value if .special=directory was set
351  $directoryLevel = 0;
352  if ($this->conf['special'] === 'directory') {
353  $value = isset($this->conf['special.']['value.']) ? $this->parent_cObj->stdWrap(
354  $this->conf['special.']['value'],
355  $this->conf['special.']['value.']
356  ) : $this->conf['special.']['value'];
357  if ($value === '') {
358  $value = $tsfe->page['uid'];
359  }
360  $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
361  }
362  // 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
363  // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
364  $startLevel = $directoryLevel ?: $this->entryLevel;
365  $currentLevel = $startLevel + $this->menuNumber;
366  if (is_array($this->tmpl->rootLine[$currentLevel])) {
367  $nextMParray = $this->MP_array;
368  if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
369  // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
370  // otherwise automatic expansion will not work
371  $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
372  if (isset($parentRecord['_MP_PARAM'])) {
373  $nextMParray[] = $parentRecord['_MP_PARAM'];
374  }
375  }
376  // In overlay mode, add next level MPvars as well:
377  if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
378  $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
379  }
380  $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
381  (!empty($nextMParray)
382  ? ':' . implode(',', $nextMParray)
383  : ''
384  );
385  } else {
386  $this->nextActive = '';
387  }
388  // imgNamePrefix
389  if ($this->mconf['imgNamePrefix']) {
390  $this->imgNamePrefix = $this->mconf['imgNamePrefix'];
391  }
392  $this->imgNameNotRandom = $this->mconf['imgNameNotRandom'];
393  $retVal = true;
394  } else {
395  $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
396  $retVal = false;
397  }
398  return $retVal;
399  }
400 
409  public function makeMenu()
410  {
411  if (!$this->id) {
412  return;
413  }
414 
415  $this->useCacheHash = false;
416 
417  // Initializing showAccessRestrictedPages
418  $SAVED_where_groupAccess = '';
419  if ($this->mconf['showAccessRestrictedPages']) {
420  // SAVING where_groupAccess
421  $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
422  // Temporarily removing fe_group checking!
423  $this->sys_page->where_groupAccess = '';
424  }
425 
426  $menuItems = $this->prepareMenuItems();
427 
428  $c = 0;
429  $c_b = 0;
430  $minItems = (int)($this->mconf['minItems'] ?: $this->conf['minItems']);
431  $maxItems = (int)($this->mconf['maxItems'] ?: $this->conf['maxItems']);
432  $begin = $this->parent_cObj->calc($this->mconf['begin'] ? $this->mconf['begin'] : $this->conf['begin']);
433  $minItemsConf = isset($this->mconf['minItems.']) ? $this->mconf['minItems.'] : (isset($this->conf['minItems.']) ? $this->conf['minItems.'] : null);
434  $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
435  $maxItemsConf = isset($this->mconf['maxItems.']) ? $this->mconf['maxItems.'] : (isset($this->conf['maxItems.']) ? $this->conf['maxItems.'] : null);
436  $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
437  $beginConf = isset($this->mconf['begin.']) ? $this->mconf['begin.'] : (isset($this->conf['begin.']) ? $this->conf['begin.'] : null);
438  $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
439  $banUidArray = $this->getBannedUids();
440  // Fill in the menuArr with elements that should go into the menu:
441  $this->menuArr = array();
442  foreach ($menuItems as $data) {
443  $spacer = GeneralUtility::inList($this->spacerIDList, $data['doktype']) || $data['ITEM_STATE'] === 'SPC';
444  // if item is a spacer, $spacer is set
445  if ($this->filterMenuPages($data, $banUidArray, $spacer)) {
446  $c_b++;
447  // If the beginning item has been reached.
448  if ($begin <= $c_b) {
449  $this->menuArr[$c] = $data;
450  $this->menuArr[$c]['isSpacer'] = $spacer;
451  $c++;
452  if ($maxItems && $c >= $maxItems) {
453  break;
454  }
455  }
456  }
457  }
458  // Fill in fake items, if min-items is set.
459  if ($minItems) {
460  while ($c < $minItems) {
461  $this->menuArr[$c] = array(
462  'title' => '...',
463  'uid' => $this->getTypoScriptFrontendController()->id
464  );
465  $c++;
466  }
467  }
468  // Passing the menuArr through a user defined function:
469  if ($this->mconf['itemArrayProcFunc']) {
470  $this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
471  }
472  // Setting number of menu items
473  $this->getTypoScriptFrontendController()->register['count_menuItems'] = count($this->menuArr);
474  $this->hash = md5(
475  serialize($this->menuArr) .
476  serialize($this->mconf) .
477  serialize($this->tmpl->rootLine) .
478  serialize($this->MP_array)
479  );
480  // Get the cache timeout:
481  if ($this->conf['cache_period']) {
482  $cacheTimeout = $this->conf['cache_period'];
483  } else {
484  $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
485  }
486  $cache = $this->getCache();
487  $cachedData = $cache->get($this->hash);
488  if (!is_array($cachedData)) {
489  $this->generate();
490  $cache->set($this->hash, $this->result, array('ident_MENUDATA'), (int)$cacheTimeout);
491  } else {
492  $this->result = $cachedData;
493  }
494  // End showAccessRestrictedPages
495  if ($this->mconf['showAccessRestrictedPages']) {
496  // RESTORING where_groupAccess
497  $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
498  }
499  }
500 
508  public function generate()
509  {
510  }
511 
515  public function writeMenu()
516  {
517  return '';
518  }
519 
526  protected function removeInaccessiblePages(array $pages)
527  {
528  $banned = $this->getBannedUids();
529  $filteredPages = array();
530  foreach ($pages as $aPage) {
531  if ($this->filterMenuPages($aPage, $banned, $aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
532  $filteredPages[$aPage['uid']] = $aPage;
533  }
534  }
535  return $filteredPages;
536  }
537 
543  protected function prepareMenuItems()
544  {
545  $menuItems = array();
546  $alternativeSortingField = trim($this->mconf['alternativeSortingField']) ?: 'sorting';
547 
548  // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
549  $additionalWhere = isset($this->mconf['additionalWhere']) ? $this->mconf['additionalWhere'] : '';
550  if (isset($this->mconf['additionalWhere.'])) {
551  $additionalWhere = $this->parent_cObj->stdWrap($additionalWhere, $this->mconf['additionalWhere.']);
552  }
553 
554  // ... only for the FIRST level of a HMENU
555  if ($this->menuNumber == 1 && $this->conf['special']) {
556  $value = isset($this->conf['special.']['value.'])
557  ? $this->parent_cObj->stdWrap($this->conf['special.']['value'], $this->conf['special.']['value.'])
558  : $this->conf['special.']['value'];
559  switch ($this->conf['special']) {
560  case 'userfunction':
561  $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
562  break;
563  case 'language':
564  $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
565  break;
566  case 'directory':
567  $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
568  break;
569  case 'list':
570  $menuItems = $this->prepareMenuItemsForListMenu($value);
571  break;
572  case 'updated':
573  $menuItems = $this->prepareMenuItemsForUpdatedMenu(
574  $value,
575  $this->mconf['alternativeSortingField'] ?: false
576  );
577  break;
578  case 'keywords':
579  $menuItems = $this->prepareMenuItemsForKeywordsMenu(
580  $value,
581  $this->mconf['alternativeSortingField'] ?: false
582  );
583  break;
584  case 'categories':
586  $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
587  $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
588  break;
589  case 'rootline':
590  $menuItems = $this->prepareMenuItemsForRootlineMenu();
591  break;
592  case 'browse':
593  $menuItems = $this->prepareMenuitemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
594  break;
595  }
596  if ($this->mconf['sectionIndex']) {
597  $sectionIndexes = array();
598  foreach ($menuItems as $page) {
599  $sectionIndexes = $sectionIndexes + $this->sectionIndex($alternativeSortingField, $page['uid']);
600  }
601  $menuItems = $sectionIndexes;
602  }
603  } elseif (is_array($this->alternativeMenuTempArray)) {
604  // Setting $menuItems array if not level 1.
605  $menuItems = $this->alternativeMenuTempArray;
606  } elseif ($this->mconf['sectionIndex']) {
607  $menuItems = $this->sectionIndex($alternativeSortingField);
608  } else {
609  // Default: Gets a hierarchical menu based on subpages of $this->id
610  $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
611  }
612  return $menuItems;
613  }
614 
622  protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
623  {
624  $menuItems = $this->parent_cObj->callUserFunction(
625  $this->conf['special.']['userFunc'],
626  array_merge($this->conf['special.'], array('value' => $specialValue, '_altSortField' => $sortingField)),
627  ''
628  );
629  if (!is_array($menuItems)) {
630  $menuItems = array();
631  }
632  return $menuItems;
633  }
634 
641  protected function prepareMenuItemsForLanguageMenu($specialValue)
642  {
643  $menuItems = array();
644  // Getting current page record NOT overlaid by any translation:
645  $tsfe = $this->getTypoScriptFrontendController();
646  $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->page['uid']);
647  // Traverse languages set up:
648  $languageItems = GeneralUtility::intExplode(',', $specialValue);
649  foreach ($languageItems as $sUid) {
650  // Find overlay record:
651  if ($sUid) {
652  $lRecs = $this->sys_page->getPageOverlay($tsfe->page['uid'], $sUid);
653  } else {
654  $lRecs = array();
655  }
656  // Checking if the "disabled" state should be set.
657  if (GeneralUtility::hideIfNotTranslated($tsfe->page['l18n_cfg']) && $sUid &&
658  empty($lRecs) || $tsfe->page['l18n_cfg'] & 1 &&
659  (!$sUid || empty($lRecs)) ||
660  !$this->conf['special.']['normalWhenNoLanguage'] && $sUid && empty($lRecs)
661  ) {
662  $iState = $tsfe->sys_language_uid == $sUid ? 'USERDEF2' : 'USERDEF1';
663  } else {
664  $iState = $tsfe->sys_language_uid == $sUid ? 'ACT' : 'NO';
665  }
666  if ($this->conf['addQueryString']) {
667  $getVars = $this->parent_cObj->getQueryArguments(
668  $this->conf['addQueryString.'],
669  array('L' => $sUid),
670  true
671  );
672  $this->analyzeCacheHashRequirements($getVars);
673  } else {
674  $getVars = '&L=' . $sUid;
675  }
676  // Adding menu item:
677  $menuItems[] = array_merge(
678  array_merge($currentPageWithNoOverlay, $lRecs),
679  array(
680  'ITEM_STATE' => $iState,
681  '_ADD_GETVARS' => $getVars,
682  '_SAFE' => true
683  )
684  );
685  }
686  return $menuItems;
687  }
688 
696  protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
697  {
698  $tsfe = $this->getTypoScriptFrontendController();
699  $databaseConnection = $this->getDatabaseConnection();
700  $menuItems = array();
701  if ($specialValue == '') {
702  $specialValue = $tsfe->page['uid'];
703  }
704  $items = GeneralUtility::intExplode(',', $specialValue);
705  foreach ($items as $id) {
706  $MP = $this->tmpl->getFromMPmap($id);
707  // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
708  $mount_info = $this->sys_page->getMountPointInfo($id);
709  if (is_array($mount_info)) {
710  if ($mount_info['overlay']) {
711  // Overlays should already have their full MPvars calculated:
712  $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
713  $MP = $MP ? $MP : $mount_info['MPvar'];
714  } else {
715  $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
716  }
717  $id = $mount_info['mount_pid'];
718  }
719  // Get sub-pages:
720  $res = $this->parent_cObj->exec_getQuery('pages', array('pidInList' => $id, 'orderBy' => $sortingField));
721  while ($row = $databaseConnection->sql_fetch_assoc($res)) {
722  $tsfe->sys_page->versionOL('pages', $row, true);
723  if (!empty($row)) {
724  // Keep mount point?
725  $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
726  // There is a valid mount point.
727  if (is_array($mount_info) && $mount_info['overlay']) {
728  // Using "getPage" is OK since we need the check for enableFields
729  // AND for type 2 of mount pids we DO require a doktype < 200!
730  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
731  if (!empty($mp_row)) {
732  $row = $mp_row;
733  $row['_MP_PARAM'] = $mount_info['MPvar'];
734  } else {
735  // If the mount point could not be fetched with respect
736  // to enableFields, unset the row so it does not become a part of the menu!
737  unset($row);
738  }
739  }
740  // Add external MP params, then the row:
741  if (!empty($row)) {
742  if ($MP) {
743  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
744  }
745  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
746  }
747  }
748  }
749  $databaseConnection->sql_free_result($res);
750  }
751  return $menuItems;
752  }
753 
760  protected function prepareMenuItemsForListMenu($specialValue)
761  {
762  $menuItems = array();
763  if ($specialValue == '') {
764  $specialValue = $this->id;
765  }
766  $skippedEnableFields = array();
767  if (!empty($this->mconf['showAccessRestrictedPages'])) {
768  $skippedEnableFields = array('fe_group' => 1);
769  }
771  $loadDB = GeneralUtility::makeInstance(RelationHandler::class);
772  $loadDB->setFetchAllFields(true);
773  $loadDB->start($specialValue, 'pages');
774  $loadDB->additionalWhere['pages'] = $this->parent_cObj->enableFields('pages', false, $skippedEnableFields);
775  $loadDB->getFromDB();
776  foreach ($loadDB->itemArray as $val) {
777  $MP = $this->tmpl->getFromMPmap($val['id']);
778  // Keep mount point?
779  $mount_info = $this->sys_page->getMountPointInfo($val['id']);
780  // There is a valid mount point.
781  if (is_array($mount_info) && $mount_info['overlay']) {
782  // Using "getPage" is OK since we need the check for enableFields
783  // AND for type 2 of mount pids we DO require a doktype < 200!
784  $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
785  if (!empty($mp_row)) {
786  $row = $mp_row;
787  $row['_MP_PARAM'] = $mount_info['MPvar'];
788  // Overlays should already have their full MPvars calculated
789  if ($mount_info['overlay']) {
790  $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
791  if ($MP) {
792  unset($row['_MP_PARAM']);
793  }
794  }
795  } else {
796  // If the mount point could not be fetched with respect to
797  // enableFields, unset the row so it does not become a part of the menu!
798  unset($row);
799  }
800  } else {
801  $row = $loadDB->results['pages'][$val['id']];
802  }
803  // Add versioning overlay for current page (to respect workspaces)
804  if (isset($row) && is_array($row)) {
805  $this->sys_page->versionOL('pages', $row, true);
806  }
807  // Add external MP params, then the row:
808  if (isset($row) && is_array($row)) {
809  if ($MP) {
810  $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
811  }
812  $menuItems[] = $this->sys_page->getPageOverlay($row);
813  }
814  }
815  return $menuItems;
816  }
817 
825  protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
826  {
827  $tsfe = $this->getTypoScriptFrontendController();
828  $menuItems = array();
829  if ($specialValue == '') {
830  $specialValue = $tsfe->page['uid'];
831  }
832  $items = GeneralUtility::intExplode(',', $specialValue);
833  if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
834  $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
835  } else {
836  $depth = 20;
837  }
838  // Max number of items
839  $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
840  $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
841  if (!$limit) {
842  $limit = 10;
843  }
844  // *'auto', 'manual', 'tstamp'
845  $mode = $this->conf['special.']['mode'];
846  // Get id's
847  $id_list_arr = array();
848  foreach ($items as $id) {
849  $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
850  $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $bA, $bA - 1);
851  }
852  $id_list = implode(',', $id_list_arr);
853  // Get sortField (mode)
854  switch ($mode) {
855  case 'starttime':
856  $sortField = 'starttime';
857  break;
858  case 'lastUpdated':
859  case 'manual':
860  $sortField = 'lastUpdated';
861  break;
862  case 'tstamp':
863  $sortField = 'tstamp';
864  break;
865  case 'crdate':
866  $sortField = 'crdate';
867  break;
868  default:
869  $sortField = 'SYS_LASTCHANGED';
870  }
871  $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
872  if ($this->conf['special.']['excludeNoSearchPages']) {
873  $extraWhere .= ' AND pages.no_search=0';
874  }
875  if ($maxAge > 0) {
876  $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
877  }
878  $res = $this->parent_cObj->exec_getQuery('pages', array(
879  'pidInList' => '0',
880  'uidInList' => $id_list,
881  'where' => $sortField . '>=0' . $extraWhere,
882  'orderBy' => $sortingField ?: $sortField . ' DESC',
883  'max' => $limit
884  ));
885  while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($res)) {
886  $tsfe->sys_page->versionOL('pages', $row, true);
887  if (is_array($row)) {
888  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
889  }
890  }
891  $this->getDatabaseConnection()->sql_free_result($res);
892  return $menuItems;
893  }
894 
902  protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
903  {
904  $tsfe = $this->getTypoScriptFrontendController();
905  $menuItems = array();
906  list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
907  if (!$specialValue) {
908  $specialValue = $tsfe->page['uid'];
909  }
910  if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
911  $kw = isset($this->conf['special.']['setKeywords.']) ? $this->parent_cObj->stdWrap($this->conf['special.']['setKeywords'], $this->conf['special.']['setKeywords.']) : $this->conf['special.']['setKeywords'];
912  } else {
913  // The page record of the 'value'.
914  $value_rec = $this->sys_page->getPage($specialValue);
915  $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
916  // keywords.
917  $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
918  }
919  // *'auto', 'manual', 'tstamp'
920  $mode = $this->conf['special.']['mode'];
921  switch ($mode) {
922  case 'starttime':
923  $sortField = 'starttime';
924  break;
925  case 'lastUpdated':
926  case 'manual':
927  $sortField = 'lastUpdated';
928  break;
929  case 'tstamp':
930  $sortField = 'tstamp';
931  break;
932  case 'crdate':
933  $sortField = 'crdate';
934  break;
935  default:
936  $sortField = 'SYS_LASTCHANGED';
937  }
938  // Depth, limit, extra where
939  if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
940  $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
941  } else {
942  $depth = 20;
943  }
944  // Max number of items
945  $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
946  $extraWhere = ' AND pages.uid<>' . $specialValue . ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
947  if ($this->conf['special.']['excludeNoSearchPages']) {
948  $extraWhere .= ' AND pages.no_search=0';
949  }
950  // Start point
951  $eLevel = $this->parent_cObj->getKey(isset($this->conf['special.']['entryLevel.'])
952  ? $this->parent_cObj->stdWrap($this->conf['special.']['entryLevel'], $this->conf['special.']['entryLevel.'])
953  : $this->conf['special.']['entryLevel'], $this->tmpl->rootLine
954  );
955  $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
956  // Which field is for keywords
957  $kfield = 'keywords';
958  if ($this->conf['special.']['keywordsField']) {
959  list($kfield) = explode(' ', trim($this->conf['special.']['keywordsField']));
960  }
961  // If there are keywords and the startuid is present
962  if ($kw && $startUid) {
963  $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
964  $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
965  $kwArr = explode(',', $kw);
966  $keyWordsWhereArr = array();
967  foreach ($kwArr as $word) {
968  $word = trim($word);
969  if ($word) {
970  $keyWordsWhereArr[] = $kfield . ' LIKE \'%' . $this->getDatabaseConnection()->quoteStr($word, 'pages') . '%\'';
971  }
972  }
973  $where = empty($keyWordsWhereArr) ? '' : '(' . implode(' OR ', $keyWordsWhereArr) . ')';
974  $res = $this->parent_cObj->exec_getQuery('pages', array(
975  'pidInList' => '0',
976  'uidInList' => $id_list,
977  'where' => $where . $extraWhere,
978  'orderBy' => $sortingField ?: $sortField . ' desc',
979  'max' => $limit
980  ));
981  while (($row = $this->getDatabaseConnection()->sql_fetch_assoc($res))) {
982  $tsfe->sys_page->versionOL('pages', $row, true);
983  if (is_array($row)) {
984  $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
985  }
986  }
987  $this->getDatabaseConnection()->sql_free_result($res);
988  }
989  return $menuItems;
990  }
991 
997  protected function prepareMenuItemsForRootlineMenu()
998  {
999  $menuItems = array();
1000  $range = isset($this->conf['special.']['range.'])
1001  ? $this->parent_cObj->stdWrap($this->conf['special.']['range'], $this->conf['special.']['range.'])
1002  : $this->conf['special.']['range'];
1003  $begin_end = explode('|', $range);
1004  $begin_end[0] = (int)$begin_end[0];
1005  if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1006  $begin_end[1] = -1;
1007  }
1008  $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1009  $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1010  if ($endKey < $beginKey) {
1011  $endKey = $beginKey;
1012  }
1013  $rl_MParray = array();
1014  foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1015  // For overlaid mount points, set the variable right now:
1016  if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1017  $rl_MParray[] = $v_rl['_MP_PARAM'];
1018  }
1019  // Traverse rootline:
1020  if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1021  $temp_key = $k_rl;
1022  $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1023  if (!empty($menuItems[$temp_key])) {
1024  // If there are no specific target for the page, put the level specific target on.
1025  if (!$menuItems[$temp_key]['target']) {
1026  $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1027  $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1028  }
1029  } else {
1030  unset($menuItems[$temp_key]);
1031  }
1032  }
1033  // For normal mount points, set the variable for next level.
1034  if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1035  $rl_MParray[] = $v_rl['_MP_PARAM'];
1036  }
1037  }
1038  // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1039  if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1040  $menuItems = array_reverse($menuItems);
1041  }
1042  return $menuItems;
1043  }
1044 
1053  protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1054  {
1055  $menuItems = array();
1056  list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
1057  if (!$specialValue) {
1058  $specialValue = $this->getTypoScriptFrontendController()->page['uid'];
1059  }
1060  // Will not work out of rootline
1061  if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1062  $recArr = array();
1063  // The page record of the 'value'.
1064  $value_rec = $this->sys_page->getPage($specialValue);
1065  // 'up' page cannot be outside rootline
1066  if ($value_rec['pid']) {
1067  // The page record of 'up'.
1068  $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1069  }
1070  // If the 'up' item was NOT level 0 in rootline...
1071  if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1072  // The page record of "index".
1073  $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1074  }
1075  // check if certain pages should be excluded
1076  $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1077  if ($this->conf['special.']['excludeNoSearchPages']) {
1078  $additionalWhere .= ' AND pages.no_search=0';
1079  }
1080  // prev / next is found
1081  $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1082  $lastKey = 0;
1083  $nextActive = 0;
1084  foreach ($prevnext_menu as $k_b => $v_b) {
1085  if ($nextActive) {
1086  $recArr['next'] = $v_b;
1087  $nextActive = 0;
1088  }
1089  if ($v_b['uid'] == $specialValue) {
1090  if ($lastKey) {
1091  $recArr['prev'] = $prevnext_menu[$lastKey];
1092  }
1093  $nextActive = 1;
1094  }
1095  $lastKey = $k_b;
1096  }
1097 
1098  $recArr['first'] = reset($prevnext_menu);
1099  $recArr['last'] = end($prevnext_menu);
1100  // prevsection / nextsection is found
1101  // You can only do this, if there is a valid page two levels up!
1102  if (is_array($recArr['index'])) {
1103  $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1104  $lastKey = 0;
1105  $nextActive = 0;
1106  foreach ($prevnextsection_menu as $k_b => $v_b) {
1107  if ($nextActive) {
1108  $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1109  if (!empty($sectionRec_temp)) {
1110  $recArr['nextsection'] = reset($sectionRec_temp);
1111  $recArr['nextsection_last'] = end($sectionRec_temp);
1112  $nextActive = 0;
1113  }
1114  }
1115  if ($v_b['uid'] == $value_rec['pid']) {
1116  if ($lastKey) {
1117  $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1118  if (!empty($sectionRec_temp)) {
1119  $recArr['prevsection'] = reset($sectionRec_temp);
1120  $recArr['prevsection_last'] = end($sectionRec_temp);
1121  }
1122  }
1123  $nextActive = 1;
1124  }
1125  $lastKey = $k_b;
1126  }
1127  }
1128  if ($this->conf['special.']['items.']['prevnextToSection']) {
1129  if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1130  $recArr['prev'] = $recArr['prevsection_last'];
1131  }
1132  if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1133  $recArr['next'] = $recArr['nextsection'];
1134  }
1135  }
1136  $items = explode('|', $this->conf['special.']['items']);
1137  $c = 0;
1138  foreach ($items as $k_b => $v_b) {
1139  $v_b = strtolower(trim($v_b));
1140  if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1141  $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1142  }
1143  if (is_array($recArr[$v_b])) {
1144  $menuItems[$c] = $recArr[$v_b];
1145  if ($this->conf['special.'][$v_b . '.']['target']) {
1146  $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1147  }
1148  $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1149  if (is_array($tmpSpecialFields)) {
1150  foreach ($tmpSpecialFields as $fk => $val) {
1151  $menuItems[$c][$fk] = $val;
1152  }
1153  }
1154  $c++;
1155  }
1156  }
1157  }
1158  return $menuItems;
1159  }
1160 
1167  protected function analyzeCacheHashRequirements($queryString)
1168  {
1169  $parameters = GeneralUtility::explodeUrl2Array($queryString);
1170  if (!empty($parameters)) {
1172  $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
1173  $cHashParameters = $cacheHashCalculator->getRelevantParameters($queryString);
1174  if (count($cHashParameters) > 1) {
1175  $this->useCacheHash = (
1176  $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ||
1177  !isset($parameters['no_cache']) ||
1178  !$parameters['no_cache']
1179  );
1180  }
1181  }
1182  }
1183 
1195  public function filterMenuPages(&$data, $banUidArray, $spacer)
1196  {
1197  $includePage = true;
1198  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'])) {
1199  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] as $classRef) {
1200  $hookObject = GeneralUtility::getUserObj($classRef);
1201  if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1202  throw new \UnexpectedValueException('$hookObject must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1203  }
1204  $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $spacer, $this);
1205  }
1206  }
1207  if (!$includePage) {
1208  return false;
1209  }
1210  if ($data['_SAFE']) {
1211  return true;
1212  }
1213 
1214  if (
1215  ($this->mconf['SPC'] || !$spacer) // If the spacer-function is not enabled, spacers will not enter the $menuArr
1216  && (!$data['nav_hide'] || $this->conf['includeNotInMenu']) // Not hidden in navigation
1217  && !GeneralUtility::inList($this->doktypeExcludeList, $data['doktype']) // Page may not be 'not_in_menu' or 'Backend User Section'
1218  && !ArrayUtility::inArray($banUidArray, $data['uid']) // not in banned uid's
1219  ) {
1220  // Checks if the default language version can be shown:
1221  // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
1222  $tsfe = $this->getTypoScriptFrontendController();
1223  $blockPage = $data['l18n_cfg'] & 1 && (!$tsfe->sys_language_uid || $tsfe->sys_language_uid && !$data['_PAGES_OVERLAY']);
1224  if (!$blockPage) {
1225  // Checking if a page should be shown in the menu depending on whether a translation exists:
1226  $tok = true;
1227  // There is an alternative language active AND the current page requires a translation:
1228  if ($tsfe->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1229  if (!$data['_PAGES_OVERLAY']) {
1230  $tok = false;
1231  }
1232  }
1233  // Continue if token is TRUE:
1234  if ($tok) {
1235  // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1236  if ($this->conf['protectLvar']) {
1237  $languageUid = (int)$tsfe->config['config']['sys_language_uid'];
1238  if ($languageUid && ($this->conf['protectLvar'] == 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
1239  $olRec = $tsfe->sys_page->getPageOverlay($data['uid'], $languageUid);
1240  if (empty($olRec)) {
1241  // If no pages_language_overlay record then page can NOT be accessed in
1242  // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1243  $data['_ADD_GETVARS'] .= '&L=0';
1244  }
1245  }
1246  }
1247  return true;
1248  }
1249  }
1250  }
1251  return false;
1252  }
1253 
1269  public function procesItemStates($splitCount)
1270  {
1271  // Prepare normal settings
1272  if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1273  // Setting a blank array if NO=1 and there are no properties.
1274  $this->mconf['NO.'] = array();
1275  }
1276  $NOconf = $this->tmpl->splitConfArray($this->mconf['NO.'], $splitCount);
1277  // Prepare rollOver settings, overriding normal settings
1278  $ROconf = array();
1279  if ($this->mconf['RO']) {
1280  $ROconf = $this->tmpl->splitConfArray($this->mconf['RO.'], $splitCount);
1281  }
1282  // Prepare IFSUB settings, overriding normal settings
1283  // IFSUB is TRUE if there exist submenu items to the current item
1284  if (!empty($this->mconf['IFSUB'])) {
1285  $IFSUBconf = null;
1286  $IFSUBROconf = null;
1287  foreach ($NOconf as $key => $val) {
1288  if ($this->isItemState('IFSUB', $key)) {
1289  // if this is the first IFSUB element, we must generate IFSUB.
1290  if ($IFSUBconf === null) {
1291  $IFSUBconf = $this->tmpl->splitConfArray($this->mconf['IFSUB.'], $splitCount);
1292  if (!empty($this->mconf['IFSUBRO'])) {
1293  $IFSUBROconf = $this->tmpl->splitConfArray($this->mconf['IFSUBRO.'], $splitCount);
1294  }
1295  }
1296  // Substitute normal with ifsub
1297  if (isset($IFSUBconf[$key])) {
1298  $NOconf[$key] = $IFSUBconf[$key];
1299  }
1300  // If rollOver on normal, we must apply a state for rollOver on the active
1301  if ($ROconf) {
1302  // If RollOver on active then apply this
1303  $ROconf[$key] = !empty($IFSUBROconf[$key]) ? $IFSUBROconf[$key] : $IFSUBconf[$key];
1304  }
1305  }
1306  }
1307  }
1308  // Prepare active settings, overriding normal settings
1309  if (!empty($this->mconf['ACT'])) {
1310  $ACTconf = null;
1311  $ACTROconf = null;
1312  // Find active
1313  foreach ($NOconf as $key => $val) {
1314  if ($this->isItemState('ACT', $key)) {
1315  // If this is the first 'active', we must generate ACT.
1316  if ($ACTconf === null) {
1317  $ACTconf = $this->tmpl->splitConfArray($this->mconf['ACT.'], $splitCount);
1318  // Prepare active rollOver settings, overriding normal active settings
1319  if (!empty($this->mconf['ACTRO'])) {
1320  $ACTROconf = $this->tmpl->splitConfArray($this->mconf['ACTRO.'], $splitCount);
1321  }
1322  }
1323  // Substitute normal with active
1324  if (isset($ACTconf[$key])) {
1325  $NOconf[$key] = $ACTconf[$key];
1326  }
1327  // If rollOver on normal, we must apply a state for rollOver on the active
1328  if ($ROconf) {
1329  // If RollOver on active then apply this
1330  $ROconf[$key] = !empty($ACTROconf[$key]) ? $ACTROconf[$key] : $ACTconf[$key];
1331  }
1332  }
1333  }
1334  }
1335  // Prepare ACT (active)/IFSUB settings, overriding normal settings
1336  // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
1337  if (!empty($this->mconf['ACTIFSUB'])) {
1338  $ACTIFSUBconf = null;
1339  $ACTIFSUBROconf = null;
1340  // Find active
1341  foreach ($NOconf as $key => $val) {
1342  if ($this->isItemState('ACTIFSUB', $key)) {
1343  // If this is the first 'active', we must generate ACTIFSUB.
1344  if ($ACTIFSUBconf === null) {
1345  $ACTIFSUBconf = $this->tmpl->splitConfArray($this->mconf['ACTIFSUB.'], $splitCount);
1346  // Prepare active rollOver settings, overriding normal active settings
1347  if (!empty($this->mconf['ACTIFSUBRO'])) {
1348  $ACTIFSUBROconf = $this->tmpl->splitConfArray($this->mconf['ACTIFSUBRO.'], $splitCount);
1349  }
1350  }
1351  // Substitute normal with active
1352  if (isset($ACTIFSUBconf[$key])) {
1353  $NOconf[$key] = $ACTIFSUBconf[$key];
1354  }
1355  // If rollOver on normal, we must apply a state for rollOver on the active
1356  if ($ROconf) {
1357  // If RollOver on active then apply this
1358  $ROconf[$key] = !empty($ACTIFSUBROconf[$key]) ? $ACTIFSUBROconf[$key] : $ACTIFSUBconf[$key];
1359  }
1360  }
1361  }
1362  }
1363  // Prepare CUR (current) settings, overriding normal settings
1364  // CUR is TRUE if the current page equals the item here!
1365  if (!empty($this->mconf['CUR'])) {
1366  $CURconf = null;
1367  $CURROconf = null;
1368  foreach ($NOconf as $key => $val) {
1369  if ($this->isItemState('CUR', $key)) {
1370  // if this is the first 'current', we must generate CUR. Basically this control is just inherited
1371  // from the other implementations as current would only exist one time and that's it
1372  // (unless you use special-features of HMENU)
1373  if ($CURconf === null) {
1374  $CURconf = $this->tmpl->splitConfArray($this->mconf['CUR.'], $splitCount);
1375  if (!empty($this->mconf['CURRO'])) {
1376  $CURROconf = $this->tmpl->splitConfArray($this->mconf['CURRO.'], $splitCount);
1377  }
1378  }
1379  // Substitute normal with current
1380  if (isset($CURconf[$key])) {
1381  $NOconf[$key] = $CURconf[$key];
1382  }
1383  // If rollOver on normal, we must apply a state for rollOver on the active
1384  if ($ROconf) {
1385  // If RollOver on active then apply this
1386  $ROconf[$key] = !empty($CURROconf[$key]) ? $CURROconf[$key] : $CURconf[$key];
1387  }
1388  }
1389  }
1390  }
1391  // Prepare CUR (current)/IFSUB settings, overriding normal settings
1392  // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
1393  if (!empty($this->mconf['CURIFSUB'])) {
1394  $CURIFSUBconf = null;
1395  $CURIFSUBROconf = null;
1396  foreach ($NOconf as $key => $val) {
1397  if ($this->isItemState('CURIFSUB', $key)) {
1398  // If this is the first 'current', we must generate CURIFSUB.
1399  if ($CURIFSUBconf === null) {
1400  $CURIFSUBconf = $this->tmpl->splitConfArray($this->mconf['CURIFSUB.'], $splitCount);
1401  // Prepare current rollOver settings, overriding normal current settings
1402  if (!empty($this->mconf['CURIFSUBRO'])) {
1403  $CURIFSUBROconf = $this->tmpl->splitConfArray($this->mconf['CURIFSUBRO.'], $splitCount);
1404  }
1405  }
1406  // Substitute normal with active
1407  if ($CURIFSUBconf[$key]) {
1408  $NOconf[$key] = $CURIFSUBconf[$key];
1409  }
1410  // If rollOver on normal, we must apply a state for rollOver on the current
1411  if ($ROconf) {
1412  // If RollOver on current then apply this
1413  $ROconf[$key] = !empty($CURIFSUBROconf[$key]) ? $CURIFSUBROconf[$key] : $CURIFSUBconf[$key];
1414  }
1415  }
1416  }
1417  }
1418  // Prepare active settings, overriding normal settings
1419  if (!empty($this->mconf['USR'])) {
1420  $USRconf = null;
1421  $USRROconf = null;
1422  // Find active
1423  foreach ($NOconf as $key => $val) {
1424  if ($this->isItemState('USR', $key)) {
1425  // if this is the first active, we must generate USR.
1426  if ($USRconf === null) {
1427  $USRconf = $this->tmpl->splitConfArray($this->mconf['USR.'], $splitCount);
1428  // Prepare active rollOver settings, overriding normal active settings
1429  if (!empty($this->mconf['USRRO'])) {
1430  $USRROconf = $this->tmpl->splitConfArray($this->mconf['USRRO.'], $splitCount);
1431  }
1432  }
1433  // Substitute normal with active
1434  if ($USRconf[$key]) {
1435  $NOconf[$key] = $USRconf[$key];
1436  }
1437  // If rollOver on normal, we must apply a state for rollOver on the active
1438  if ($ROconf) {
1439  // If RollOver on active then apply this
1440  $ROconf[$key] = !empty($USRROconf[$key]) ? $USRROconf[$key] : $USRconf[$key];
1441  }
1442  }
1443  }
1444  }
1445  // Prepare spacer settings, overriding normal settings
1446  if (!empty($this->mconf['SPC'])) {
1447  $SPCconf = null;
1448  // Find spacers
1449  foreach ($NOconf as $key => $val) {
1450  if ($this->isItemState('SPC', $key)) {
1451  // If this is the first spacer, we must generate SPC.
1452  if ($SPCconf === null) {
1453  $SPCconf = $this->tmpl->splitConfArray($this->mconf['SPC.'], $splitCount);
1454  }
1455  // Substitute normal with spacer
1456  if (isset($SPCconf[$key])) {
1457  $NOconf[$key] = $SPCconf[$key];
1458  }
1459  }
1460  }
1461  }
1462  // Prepare Userdefined settings
1463  if (!empty($this->mconf['USERDEF1'])) {
1464  $USERDEF1conf = null;
1465  $USERDEF1ROconf = null;
1466  // Find active
1467  foreach ($NOconf as $key => $val) {
1468  if ($this->isItemState('USERDEF1', $key)) {
1469  // If this is the first active, we must generate USERDEF1.
1470  if ($USERDEF1conf === null) {
1471  $USERDEF1conf = $this->tmpl->splitConfArray($this->mconf['USERDEF1.'], $splitCount);
1472  // Prepare active rollOver settings, overriding normal active settings
1473  if (!empty($this->mconf['USERDEF1RO'])) {
1474  $USERDEF1ROconf = $this->tmpl->splitConfArray($this->mconf['USERDEF1RO.'], $splitCount);
1475  }
1476  }
1477  // Substitute normal with active
1478  if (isset($USERDEF1conf[$key])) {
1479  $NOconf[$key] = $USERDEF1conf[$key];
1480  }
1481  // If rollOver on normal, we must apply a state for rollOver on the active
1482  if ($ROconf) {
1483  // If RollOver on active then apply this
1484  $ROconf[$key] = !empty($USERDEF1ROconf[$key]) ? $USERDEF1ROconf[$key] : $USERDEF1conf[$key];
1485  }
1486  }
1487  }
1488  }
1489  // Prepare Userdefined settings
1490  if (!empty($this->mconf['USERDEF2'])) {
1491  $USERDEF2conf = null;
1492  $USERDEF2ROconf = null;
1493  // Find active
1494  foreach ($NOconf as $key => $val) {
1495  if ($this->isItemState('USERDEF2', $key)) {
1496  // If this is the first active, we must generate USERDEF2.
1497  if ($USERDEF2conf) {
1498  $USERDEF2conf = $this->tmpl->splitConfArray($this->mconf['USERDEF2.'], $splitCount);
1499  // Prepare active rollOver settings, overriding normal active settings
1500  if (!empty($this->mconf['USERDEF2RO'])) {
1501  $USERDEF2ROconf = $this->tmpl->splitConfArray($this->mconf['USERDEF2RO.'], $splitCount);
1502  }
1503  }
1504  // Substitute normal with active
1505  if (isset($USERDEF2conf[$key])) {
1506  $NOconf[$key] = $USERDEF2conf[$key];
1507  }
1508  // If rollOver on normal, we must apply a state for rollOver on the active
1509  if ($ROconf) {
1510  // If RollOver on active then apply this
1511  $ROconf[$key] = !empty($USERDEF2ROconf[$key]) ? $USERDEF2ROconf[$key] : $USERDEF2conf[$key];
1512  }
1513  }
1514  }
1515  }
1516  return array($NOconf, $ROconf);
1517  }
1518 
1529  public function link($key, $altTarget = '', $typeOverride = '')
1530  {
1531  // Mount points:
1532  $MP_var = $this->getMPvar($key);
1533  $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1534  // Setting override ID
1535  if ($this->mconf['overrideId'] || $this->menuArr[$key]['overrideId']) {
1536  $overrideArray = array();
1537  // If a user script returned the value overrideId in the menu array we use that as page id
1538  $overrideArray['uid'] = $this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId'];
1539  $overrideArray['alias'] = '';
1540  // Clear MP parameters since ID was changed.
1541  $MP_params = '';
1542  } else {
1543  $overrideArray = '';
1544  }
1545  // Setting main target:
1546  if ($altTarget) {
1547  $mainTarget = $altTarget;
1548  } elseif ($this->mconf['target.']) {
1549  $mainTarget = $this->parent_cObj->stdWrap($this->mconf['target'], $this->mconf['target.']);
1550  } else {
1551  $mainTarget = $this->mconf['target'];
1552  }
1553  // Creating link:
1554  $addParams = $this->mconf['addParams'] . $MP_params;
1555  if ($this->mconf['collapse'] && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1556  $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1557  $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1558  $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1559  } else {
1560  $addParams .= $this->I['val']['additionalParams'] . $this->menuArr[$key]['_ADD_GETVARS'];
1561  $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1562  }
1563  // Override URL if using "External URL"
1564  if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_LINK) {
1565  if ($this->menuArr[$key]['urltype'] == 3 && GeneralUtility::validEmail($this->menuArr[$key]['url'])) {
1566  // Create mailto-link using \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink (concerning spamProtectEmailAddresses):
1567  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(array('parameter' => $this->menuArr[$key]['url']));
1568  $LD['target'] = '';
1569  } else {
1570  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(array('parameter' => $this->getSysPage()->getExtURL($this->menuArr[$key])));
1571  }
1572  }
1573 
1574  $tsfe = $this->getTypoScriptFrontendController();
1575 
1576  // Override url if current page is a shortcut
1577  $shortcut = null;
1578  if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_SHORTCUT && $this->menuArr[$key]['shortcut_mode'] != PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1579  $menuItem = $this->determineOriginalShortcutPage($this->menuArr[$key]);
1580  try {
1581  $shortcut = $tsfe->getPageShortcut(
1582  $menuItem['shortcut'],
1583  $menuItem['shortcut_mode'],
1584  $menuItem['uid'],
1585  20,
1586  array(),
1587  true
1588  );
1589  } catch (\Exception $ex) {
1590  }
1591  if (!is_array($shortcut)) {
1592  return array();
1593  }
1594  // Only setting url, not target
1595  $LD['totalURL'] = $this->parent_cObj->typoLink_URL(array(
1596  'parameter' => $shortcut['uid'],
1597  'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1598  'linkAccessRestrictedPages' => $this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE'
1599  ));
1600  }
1601  if ($shortcut) {
1602  $pageData = $shortcut;
1603  $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1604  } else {
1605  $pageData = $this->menuArr[$key];
1606  }
1607  // Manipulation in case of access restricted pages:
1608  $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1609  // Overriding URL / Target if set to do so:
1610  if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
1611  $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1612  if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1613  $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1614  }
1615  }
1616  // OnClick open in windows.
1617  $onClick = '';
1618  if ($this->mconf['JSWindow']) {
1619  $conf = $this->mconf['JSWindow.'];
1620  $url = $LD['totalURL'];
1621  $LD['totalURL'] = '#';
1622  $onClick = 'openPic('
1623  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1624  . '\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1625  . GeneralUtility::quoteJSvalue($conf['params']) . '); return false;';
1626  $tsfe->setJS('openPic');
1627  }
1628  // look for type and popup
1629  // following settings are valid in field target:
1630  // 230 will add type=230 to the link
1631  // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1632  // 230 _blank will add type=230 to the link and open with target "_blank"
1633  // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1634  $matches = array();
1635  $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1636  if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) || $targetIsType) {
1637  // has type?
1638  if ((int)$matches[1] || $targetIsType) {
1639  $LD['totalURL'] = $this->parent_cObj->URLqMark($LD['totalURL'], '&type=' . ($targetIsType ?: (int)$matches[1]));
1640  $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1641  }
1642  // Open in popup window?
1643  if ($matches[3] && $matches[4]) {
1644  $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1645  $onClick = 'vHWin=window.open('
1646  . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1647  . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1648  $LD['target'] = '';
1649  }
1650  }
1651  // out:
1652  $list = array();
1653  // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1654  // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1655  // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1656  $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1657  $list['TARGET'] = $LD['target'];
1658  $list['onClick'] = $onClick;
1659  return $list;
1660  }
1661 
1673  protected function determineOriginalShortcutPage(array $page)
1674  {
1675  // Check if modification is required
1676  if (
1677  $this->getTypoScriptFrontendController()->sys_language_uid > 0
1678  && empty($page['shortcut'])
1679  && !empty($page['uid'])
1680  && !empty($page['_PAGES_OVERLAY'])
1681  && !empty($page['_PAGES_OVERLAY_UID'])
1682  ) {
1683  // Using raw record since the record was overlaid and is correct already:
1684  $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1685 
1686  if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1687  $page['shortcut'] = $originalPage['shortcut'];
1688  }
1689  }
1690 
1691  return $page;
1692  }
1693 
1703  public function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1704  {
1705  // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1706  if ($this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1707  $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1708  $addParams = str_replace(
1709  array(
1710  '###RETURN_URL###',
1711  '###PAGE_ID###'
1712  ),
1713  array(
1714  rawurlencode($LD['totalURL']),
1715  isset($page['_SHORTCUT_PAGE_UID']) ? $page['_SHORTCUT_PAGE_UID'] : $page['uid']
1716  ),
1717  $this->mconf['showAccessRestrictedPages.']['addParams']
1718  );
1719  $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', '', $addParams, $typeOverride);
1720  }
1721  }
1722 
1731  public function subMenu($uid, $objSuffix = '')
1732  {
1733  // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1734  $altArray = '';
1735  if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU']) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1736  $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1737  }
1738  // Make submenu if the page is the next active
1739  $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix];
1740  // stdWrap for expAll
1741  if (isset($this->mconf['expAll.'])) {
1742  $this->mconf['expAll'] = $this->parent_cObj->stdWrap($this->mconf['expAll'], $this->mconf['expAll.']);
1743  }
1744  if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !$this->mconf['sectionIndex']) {
1745  try {
1746  $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1748  $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1749  $submenu->entryLevel = $this->entryLevel + 1;
1750  $submenu->rL_uidRegister = $this->rL_uidRegister;
1751  $submenu->MP_array = $this->MP_array;
1752  if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1753  $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1754  }
1755  // Especially scripts that build the submenu needs the parent data
1756  $submenu->parent_cObj = $this->parent_cObj;
1757  // Setting alternativeMenuTempArray (will be effective only if an array)
1758  if (is_array($altArray)) {
1759  $submenu->alternativeMenuTempArray = $altArray;
1760  }
1761  if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1762  $submenu->makeMenu();
1763  // Memorize the current menu item count
1764  $tsfe = $this->getTypoScriptFrontendController();
1765  $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1766  // Reset the menu item count for the submenu
1767  $tsfe->register['count_MENUOBJ'] = 0;
1768  $content = $submenu->writeMenu();
1769  // Restore the item count now that the submenu has been handled
1770  $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1771  $tsfe->register['count_menuItems'] = count($this->menuArr);
1772  return $content;
1773  }
1774  } catch (Exception\NoSuchMenuTypeException $e) {
1775  }
1776  }
1777  return '';
1778  }
1779 
1789  public function isNext($uid, $MPvar = '')
1790  {
1791  // Check for always active PIDs:
1792  if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1793  return true;
1794  }
1795  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1796  if ($uid && $testUid == $this->nextActive) {
1797  return true;
1798  }
1799  return false;
1800  }
1801 
1810  public function isActive($uid, $MPvar = '')
1811  {
1812  // Check for always active PIDs:
1813  if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1814  return true;
1815  }
1816  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1817  if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1818  return true;
1819  }
1820  return false;
1821  }
1822 
1831  public function isCurrent($uid, $MPvar = '')
1832  {
1833  $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1834  return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1835  }
1836 
1845  public function isSubMenu($uid)
1846  {
1847  // Looking for a mount-pid for this UID since if that
1848  // exists we should look for a subpages THERE and not in the input $uid;
1849  $mount_info = $this->sys_page->getMountPointInfo($uid);
1850  if (is_array($mount_info)) {
1851  $uid = $mount_info['mount_pid'];
1852  }
1853  $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1854  $hasSubPages = false;
1855  $bannedUids = $this->getBannedUids();
1856  foreach ($recs as $theRec) {
1857  // no valid subpage if the document type is excluded from the menu
1858  if (GeneralUtility::inList($this->doktypeExcludeList, $theRec['doktype'])) {
1859  continue;
1860  }
1861  // No valid subpage if the page is hidden inside menus and
1862  // it wasn't forced to show such entries
1863  if ($theRec['nav_hide'] && !$this->conf['includeNotInMenu']) {
1864  continue;
1865  }
1866  // No valid subpage if the default language should be shown and the page settings
1867  // are excluding the visibility of the default language
1868  if (!$this->getTypoScriptFrontendController()->sys_language_uid && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
1869  continue;
1870  }
1871  // No valid subpage if the alternative language should be shown and the page settings
1872  // are requiring a valid overlay but it doesn't exists
1873  $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg']);
1874  if ($this->getTypoScriptFrontendController()->sys_language_uid && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1875  continue;
1876  }
1877  // No valid subpage if the subpage is banned by excludeUidList
1878  if (in_array($theRec['uid'], $bannedUids)) {
1879  continue;
1880  }
1881  $hasSubPages = true;
1882  break;
1883  }
1884  return $hasSubPages;
1885  }
1886 
1896  public function isItemState($kind, $key)
1897  {
1898  $natVal = false;
1899  // If any value is set for ITEM_STATE the normal evaluation is discarded
1900  if ($this->menuArr[$key]['ITEM_STATE']) {
1901  if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1902  $natVal = true;
1903  }
1904  } else {
1905  switch ($kind) {
1906  case 'SPC':
1907  $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1908  break;
1909  case 'IFSUB':
1910  $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1911  break;
1912  case 'ACT':
1913  $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1914  break;
1915  case 'ACTIFSUB':
1916  $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1917  break;
1918  case 'CUR':
1919  $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
1920  break;
1921  case 'CURIFSUB':
1922  $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1923  break;
1924  case 'USR':
1925  $natVal = (bool)$this->menuArr[$key]['fe_group'];
1926  break;
1927  }
1928  }
1929  return $natVal;
1930  }
1931 
1939  public function accessKey($title)
1940  {
1941  $tsfe = $this->getTypoScriptFrontendController();
1942  // The global array ACCESSKEY is used to globally control if letters are already used!!
1943  $result = array();
1944  $title = trim(strip_tags($title));
1945  $titleLen = strlen($title);
1946  for ($a = 0; $a < $titleLen; $a++) {
1947  $key = strtoupper(substr($title, $a, 1));
1948  if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
1949  $tsfe->accessKey[$key] = 1;
1950  $result['code'] = ' accesskey="' . $key . '"';
1951  $result['alt'] = ' (ALT+' . $key . ')';
1952  $result['key'] = $key;
1953  break;
1954  }
1955  }
1956  return $result;
1957  }
1958 
1968  public function userProcess($mConfKey, $passVar)
1969  {
1970  if ($this->mconf[$mConfKey]) {
1971  $funcConf = $this->mconf[$mConfKey . '.'];
1972  $funcConf['parentObj'] = $this;
1973  $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
1974  }
1975  return $passVar;
1976  }
1977 
1984  public function setATagParts()
1985  {
1986  $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
1987  $params = $params !== '' ? ' ' . $params : '';
1988  $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], 1) . $params . '>';
1989  $this->I['A2'] = '</a>';
1990  }
1991 
2000  public function getPageTitle($title, $nav_title)
2001  {
2002  return trim($nav_title) !== '' ? $nav_title : $title;
2003  }
2004 
2012  public function getMPvar($key)
2013  {
2014  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
2015  $localMP_array = $this->MP_array;
2016  // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
2017  if ($this->menuArr[$key]['_MP_PARAM']) {
2018  $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
2019  }
2020  return !empty($localMP_array) ? implode(',', $localMP_array) : '';
2021  }
2022  return '';
2023  }
2024 
2031  public function getDoktypeExcludeWhere()
2032  {
2033  return $this->doktypeExcludeList ? ' AND pages.doktype NOT IN (' . $this->doktypeExcludeList . ')' : '';
2034  }
2035 
2042  public function getBannedUids()
2043  {
2044  $excludeUidList = isset($this->conf['excludeUidList.'])
2045  ? $this->parent_cObj->stdWrap($this->conf['excludeUidList'], $this->conf['excludeUidList.'])
2046  : $this->conf['excludeUidList'];
2047 
2048  if (!trim($excludeUidList)) {
2049  return array();
2050  }
2051 
2052  $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page['uid'], $excludeUidList);
2053  return GeneralUtility::intExplode(',', $banUidList);
2054  }
2055 
2068  public function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '')
2069  {
2070  $conf = array(
2071  'parameter' => is_array($overrideArray) && $overrideArray['uid'] ? $overrideArray['uid'] : $page['uid']
2072  );
2073  if (MathUtility::canBeInterpretedAsInteger($typeOverride)) {
2074  $conf['parameter'] .= ',' . (int)$typeOverride;
2075  }
2076  if ($addParams) {
2077  $conf['additionalParams'] = $addParams;
2078  }
2079  if ($no_cache) {
2080  $conf['no_cache'] = true;
2081  } elseif ($this->useCacheHash) {
2082  $conf['useCacheHash'] = true;
2083  }
2084  if ($oTarget) {
2085  $conf['target'] = $oTarget;
2086  }
2087  if ($page['sectionIndex_uid']) {
2088  $conf['section'] = $page['sectionIndex_uid'];
2089  }
2090  $conf['linkAccessRestrictedPages'] = $this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE';
2091  $this->parent_cObj->typoLink('|', $conf);
2092  $LD = $this->parent_cObj->lastTypoLinkLD;
2093  $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
2094  return $LD;
2095  }
2096 
2108  protected function sectionIndex($altSortField, $pid = null)
2109  {
2110  $pid = (int)($pid ?: $this->id);
2111  $basePageRow = $this->sys_page->getPage($pid);
2112  if (!is_array($basePageRow)) {
2113  return array();
2114  }
2115  $tsfe = $this->getTypoScriptFrontendController();
2116  $configuration = $this->mconf['sectionIndex.'];
2117  $useColPos = 0;
2118  if (trim($configuration['useColPos']) !== '' || is_array($configuration['useColPos.'])) {
2119  $useColPos = $tsfe->cObj->stdWrap($configuration['useColPos'], $configuration['useColPos.']);
2120  $useColPos = (int)$useColPos;
2121  }
2122  $selectSetup = array(
2123  'pidInList' => $pid,
2124  'orderBy' => $altSortField,
2125  'languageField' => 'sys_language_uid',
2126  'where' => $useColPos >= 0 ? 'colPos=' . $useColPos : ''
2127  );
2128  if ($basePageRow['content_from_pid']) {
2129  // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
2130  // the referenced page
2131  $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
2132  }
2133  $resource = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
2134  if (!$resource) {
2135  $message = 'SectionIndex: Query to fetch the content elements failed!';
2136  throw new \UnexpectedValueException($message, 1337334849);
2137  }
2138  $result = array();
2139  while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($resource)) {
2140  $this->sys_page->versionOL('tt_content', $row);
2141  if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
2142  $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL);
2143  }
2144  if ($this->mconf['sectionIndex.']['type'] !== 'all') {
2145  $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
2146  $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
2147  $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
2148  if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
2149  continue;
2150  }
2151  }
2152  if (is_array($row)) {
2153  $uid = $row['uid'];
2154  $result[$uid] = $basePageRow;
2155  $result[$uid]['title'] = $row['header'];
2156  $result[$uid]['nav_title'] = $row['header'];
2157  // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
2158  $result[$uid]['nav_hide'] = 0;
2159  $result[$uid]['subtitle'] = $row['subheader'];
2160  $result[$uid]['starttime'] = $row['starttime'];
2161  $result[$uid]['endtime'] = $row['endtime'];
2162  $result[$uid]['fe_group'] = $row['fe_group'];
2163  $result[$uid]['media'] = $row['media'];
2164  $result[$uid]['header_layout'] = $row['header_layout'];
2165  $result[$uid]['bodytext'] = $row['bodytext'];
2166  $result[$uid]['image'] = $row['image'];
2167  $result[$uid]['sectionIndex_uid'] = $uid;
2168  }
2169  }
2170  $this->getDatabaseConnection()->sql_free_result($resource);
2171  return $result;
2172  }
2173 
2179  public function getSysPage()
2180  {
2181  return $this->sys_page;
2182  }
2183 
2189  public function getParentContentObject()
2190  {
2191  return $this->parent_cObj;
2192  }
2193 
2197  protected function getDatabaseConnection()
2198  {
2199  return $GLOBALS['TYPO3_DB'];
2200  }
2201 
2205  protected function getTypoScriptFrontendController()
2206  {
2207  return $GLOBALS['TSFE'];
2208  }
2209 
2213  protected function getTimeTracker()
2214  {
2215  return $GLOBALS['TT'];
2216  }
2217 
2221  protected function getCache()
2222  {
2223  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2224  }
2225 }