2 namespace TYPO3\CMS\Frontend\ContentObject\Menu
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\Cache\CacheManager
;
18 use TYPO3\CMS\Core\Database\DatabaseConnection
;
19 use TYPO3\CMS\Core\Database\RelationHandler
;
20 use TYPO3\CMS\Core\TimeTracker\TimeTracker
;
21 use TYPO3\CMS\Core\TypoScript\TemplateService
;
22 use TYPO3\CMS\Core\Utility\ArrayUtility
;
23 use TYPO3\CMS\Core\Utility\GeneralUtility
;
24 use TYPO3\CMS\Core\Utility\MathUtility
;
25 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
;
26 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
;
27 use TYPO3\CMS\Frontend\Page\CacheHashCalculator
;
28 use TYPO3\CMS\Frontend\Page\PageRepository
;
31 * Generating navigation/menus from TypoScript
33 * The HMENU content object uses this (or more precisely one of the extension classes).
34 * Among others the class generates an array of menu items. Thereafter functions from the subclasses are called.
35 * The class is always used through extension classes (like GraphicalMenuContentObject or TextMenuContentObject).
37 abstract class AbstractMenuContentObject
40 * tells you which menu number this is. This is important when getting data from the setup
44 public $menuNumber = 1;
51 public $entryLevel = 0;
54 * The doktype-number that defines a spacer
58 public $spacerIDList = '199';
61 * Doktypes that define which should not be included in a menu
65 public $doktypeExcludeList = '6';
70 public $alwaysActivePIDlist = array();
75 public $imgNamePrefix = 'img';
80 public $imgNameNotRandom = 0;
88 * Loaded with the parent cObj-object when a new HMENU is made
90 * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
92 public $parent_cObj = null;
97 public $GMENU_fixKey = 'gmenu';
100 * accumulation of mount point data
104 public $MP_array = array();
107 * HMENU configuration
111 public $conf = array();
114 * xMENU configuration (TMENU, GMENU etc)
118 public $mconf = array();
121 * @var \TYPO3\CMS\Core\TypoScript\TemplateService
126 * @var \TYPO3\CMS\Frontend\Page\PageRepository
128 public $sys_page = null;
131 * The base page-id of the menu.
138 * Holds the page uid of the NEXT page in the root line from the page pointed to by entryLevel;
139 * Used to expand the menu automatically if in a certain root line.
146 * The array of menuItems which is built
160 public $result = array();
163 * Is filled with an array of page uid numbers + RL parameters which are in the current
164 * root line (used to evaluate whether a menu item is in active state)
168 public $rL_uidRegister = '';
188 public $WMfreezePrefix;
198 public $WMsubmenuObjSuffixes;
203 public $WMextraScript;
206 * @var ContentObjectRenderer
208 public $WMcObj = null;
211 * Can be set to contain menu item arrays for sub-levels.
215 public $alternativeMenuTempArray = '';
218 * Will be 'id' in XHTML-mode
222 public $nameAttribute = 'name';
225 * TRUE to use cHash in generated link (normally only for the language
226 * selector and if parameters exist in the URL).
230 protected $useCacheHash = false;
233 * The initialization of the object. This just sets some internal variables.
235 * @param TemplateService $tmpl The $this->getTypoScriptFrontendController()->tmpl object
236 * @param PageRepository $sys_page The $this->getTypoScriptFrontendController()->sys_page object
237 * @param int|string $id A starting point page id. This should probably be blank since the 'entryLevel' value will be used then.
238 * @param array $conf The TypoScript configuration for the HMENU cObject
239 * @param int $menuNumber Menu number; 1,2,3. Should probably be 1
240 * @param string $objSuffix Submenu Object suffix. This offers submenus a way to use alternative configuration for specific positions in the menu; By default "1 = TMENU" would use "1." for the TMENU configuration, but if this string is set to eg. "a" then "1a." would be used for configuration instead (while "1 = " is still used for the overall object definition of "TMENU")
241 * @return bool Returns TRUE on success
242 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::HMENU()
244 public function start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix = '')
246 $tsfe = $this->getTypoScriptFrontendController();
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 and HTML5 there is no "name" attribute anymore
254 switch ($tsfe->xhtmlDoctype
) {
256 // intended fall-through
258 // intended fall-through
260 // intended fall-through
262 // intended fall-through
264 // empty means that it's HTML5 by default
265 $this->nameAttribute
= 'id';
268 $this->nameAttribute
= 'name';
270 // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the sys_page object
271 if ($this->conf
[$this->menuNumber
. $objSuffix] && is_object($tmpl) && is_object($sys_page)) {
273 $this->sys_page
= $sys_page;
274 // alwaysActivePIDlist initialized:
275 if (trim($this->conf
['alwaysActivePIDlist']) ||
isset($this->conf
['alwaysActivePIDlist.'])) {
276 if (isset($this->conf
['alwaysActivePIDlist.'])) {
277 $this->conf
['alwaysActivePIDlist'] = $this->parent_cObj
->stdWrap(
278 $this->conf
['alwaysActivePIDlist'],
279 $this->conf
['alwaysActivePIDlist.']
282 $this->alwaysActivePIDlist
= GeneralUtility
::intExplode(',', $this->conf
['alwaysActivePIDlist']);
284 // 'not in menu' doktypes
285 if ($this->conf
['excludeDoktypes']) {
286 $this->doktypeExcludeList
= $this->getDatabaseConnection()->cleanIntList($this->conf
['excludeDoktypes']);
289 $this->entryLevel
= $this->parent_cObj
->getKey(
290 isset($conf['entryLevel.']) ?
$this->parent_cObj
->stdWrap(
293 ) : $conf['entryLevel'],
294 $this->tmpl
->rootLine
296 // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
297 // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
299 $this->id
= (int)$id;
301 // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
302 $this->id
= (int)$this->tmpl
->rootLine
[$this->entryLevel
]['uid'];
303 // Traverse rootline to build MP_array of pages BEFORE the entryLevel
304 // (MP var for ->id is picked up in the next part of the code...)
305 foreach ($this->tmpl
->rootLine
as $entryLevel => $levelRec) {
306 // For overlaid mount points, set the variable right now:
307 if ($levelRec['_MP_PARAM'] && $levelRec['_MOUNT_OL']) {
308 $this->MP_array
[] = $levelRec['_MP_PARAM'];
310 // Break when entry level is reached:
311 if ($entryLevel >= $this->entryLevel
) {
314 // For normal mount points, set the variable for next level.
315 if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
316 $this->MP_array
[] = $levelRec['_MP_PARAM'];
320 // Return FALSE if no page ID was set (thus no menu of subpages can be made).
321 if ($this->id
<= 0) {
324 // Check if page is a mount point, and if so set id and MP_array
325 // (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...)
326 $mount_info = $this->sys_page
->getMountPointInfo($this->id
);
327 if (is_array($mount_info)) {
328 $this->MP_array
[] = $mount_info['MPvar'];
329 $this->id
= $mount_info['mount_pid'];
331 // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
332 // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
333 if (!is_array($this->rL_uidRegister
)) {
334 $rl_MParray = array();
335 foreach ($this->tmpl
->rootLine
as $v_rl) {
336 // For overlaid mount points, set the variable right now:
337 if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
338 $rl_MParray[] = $v_rl['_MP_PARAM'];
341 $this->rL_uidRegister
[] = 'ITEM:' . $v_rl['uid'] .
343 ?
':' . implode(',', $rl_MParray)
346 // For normal mount points, set the variable for next level.
347 if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
348 $rl_MParray[] = $v_rl['_MP_PARAM'];
352 // Set $directoryLevel so the following evalution of the nextActive will not return
353 // an invalid value if .special=directory was set
355 if ($this->conf
['special'] === 'directory') {
356 $value = isset($this->conf
['special.']['value.']) ?
$this->parent_cObj
->stdWrap(
357 $this->conf
['special.']['value'],
358 $this->conf
['special.']['value.']
359 ) : $this->conf
['special.']['value'];
361 $value = $tsfe->page
['uid'];
363 $directoryLevel = (int)$tsfe->tmpl
->getRootlineLevel($value);
365 // 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
366 // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
367 $startLevel = $directoryLevel ?
: $this->entryLevel
;
368 $currentLevel = $startLevel +
$this->menuNumber
;
369 if (is_array($this->tmpl
->rootLine
[$currentLevel])) {
370 $nextMParray = $this->MP_array
;
371 if (empty($nextMParray) && !$this->tmpl
->rootLine
[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
372 // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
373 // otherwise automatic expansion will not work
374 $parentRecord = $this->tmpl
->rootLine
[$currentLevel - 1];
375 if (isset($parentRecord['_MP_PARAM'])) {
376 $nextMParray[] = $parentRecord['_MP_PARAM'];
379 // In overlay mode, add next level MPvars as well:
380 if ($this->tmpl
->rootLine
[$currentLevel]['_MOUNT_OL']) {
381 $nextMParray[] = $this->tmpl
->rootLine
[$currentLevel]['_MP_PARAM'];
383 $this->nextActive
= $this->tmpl
->rootLine
[$currentLevel]['uid'] .
384 (!empty($nextMParray)
385 ?
':' . implode(',', $nextMParray)
389 $this->nextActive
= '';
392 if ($this->mconf
['imgNamePrefix']) {
393 $this->imgNamePrefix
= $this->mconf
['imgNamePrefix'];
395 $this->imgNameNotRandom
= $this->mconf
['imgNameNotRandom'];
398 $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
405 * Creates the menu in the internal variables, ready for output.
406 * Basically this will read the page records needed and fill in the internal $this->menuArr
407 * Based on a hash of this array and some other variables the $this->result variable will be
408 * loaded either from cache OR by calling the generate() method of the class to create the menu for real.
412 public function makeMenu()
418 $this->useCacheHash
= false;
420 // Initializing showAccessRestrictedPages
421 $SAVED_where_groupAccess = '';
422 if ($this->mconf
['showAccessRestrictedPages']) {
423 // SAVING where_groupAccess
424 $SAVED_where_groupAccess = $this->sys_page
->where_groupAccess
;
425 // Temporarily removing fe_group checking!
426 $this->sys_page
->where_groupAccess
= '';
429 $menuItems = $this->prepareMenuItems();
433 $minItems = (int)($this->mconf
['minItems'] ?
: $this->conf
['minItems']);
434 $maxItems = (int)($this->mconf
['maxItems'] ?
: $this->conf
['maxItems']);
435 $begin = $this->parent_cObj
->calc($this->mconf
['begin'] ?
$this->mconf
['begin'] : $this->conf
['begin']);
436 $minItemsConf = isset($this->mconf
['minItems.']) ?
$this->mconf
['minItems.'] : (isset($this->conf
['minItems.']) ?
$this->conf
['minItems.'] : null);
437 $minItems = is_array($minItemsConf) ?
$this->parent_cObj
->stdWrap($minItems, $minItemsConf) : $minItems;
438 $maxItemsConf = isset($this->mconf
['maxItems.']) ?
$this->mconf
['maxItems.'] : (isset($this->conf
['maxItems.']) ?
$this->conf
['maxItems.'] : null);
439 $maxItems = is_array($maxItemsConf) ?
$this->parent_cObj
->stdWrap($maxItems, $maxItemsConf) : $maxItems;
440 $beginConf = isset($this->mconf
['begin.']) ?
$this->mconf
['begin.'] : (isset($this->conf
['begin.']) ?
$this->conf
['begin.'] : null);
441 $begin = is_array($beginConf) ?
$this->parent_cObj
->stdWrap($begin, $beginConf) : $begin;
442 $banUidArray = $this->getBannedUids();
443 // Fill in the menuArr with elements that should go into the menu:
444 $this->menuArr
= array();
445 foreach ($menuItems as $data) {
446 $spacer = GeneralUtility
::inList($this->spacerIDList
, $data['doktype']) ||
$data['ITEM_STATE'] === 'SPC';
447 // if item is a spacer, $spacer is set
448 if ($this->filterMenuPages($data, $banUidArray, $spacer)) {
450 // If the beginning item has been reached.
451 if ($begin <= $c_b) {
452 $this->menuArr
[$c] = $data;
453 $this->menuArr
[$c]['isSpacer'] = $spacer;
455 if ($maxItems && $c >= $maxItems) {
461 // Fill in fake items, if min-items is set.
463 while ($c < $minItems) {
464 $this->menuArr
[$c] = array(
466 'uid' => $this->getTypoScriptFrontendController()->id
471 // Passing the menuArr through a user defined function:
472 if ($this->mconf
['itemArrayProcFunc']) {
473 $this->menuArr
= $this->userProcess('itemArrayProcFunc', $this->menuArr
);
475 // Setting number of menu items
476 $this->getTypoScriptFrontendController()->register
['count_menuItems'] = count($this->menuArr
);
478 serialize($this->menuArr
) .
479 serialize($this->mconf
) .
480 serialize($this->tmpl
->rootLine
) .
481 serialize($this->MP_array
)
483 // Get the cache timeout:
484 if ($this->conf
['cache_period']) {
485 $cacheTimeout = $this->conf
['cache_period'];
487 $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
489 $cache = $this->getCache();
490 $cachedData = $cache->get($this->hash
);
491 if (!is_array($cachedData)) {
493 $cache->set($this->hash
, $this->result
, array('ident_MENUDATA'), (int)$cacheTimeout);
495 $this->result
= $cachedData;
497 // End showAccessRestrictedPages
498 if ($this->mconf
['showAccessRestrictedPages']) {
499 // RESTORING where_groupAccess
500 $this->sys_page
->where_groupAccess
= $SAVED_where_groupAccess;
505 * Generates the the menu data.
507 * Subclasses should overwrite this method.
511 public function generate()
516 * @return string The HTML for the menu
518 public function writeMenu()
524 * Gets an array of page rows and removes all, which are not accessible
526 * @param array $pages
529 protected function removeInaccessiblePages(array $pages)
531 $banned = $this->getBannedUids();
532 $filteredPages = array();
533 foreach ($pages as $aPage) {
534 if ($this->filterMenuPages($aPage, $banned, $aPage['doktype'] === PageRepository
::DOKTYPE_SPACER
)) {
535 $filteredPages[$aPage['uid']] = $aPage;
538 return $filteredPages;
542 * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
546 protected function prepareMenuItems()
548 $menuItems = array();
549 $alternativeSortingField = trim($this->mconf
['alternativeSortingField']) ?
: 'sorting';
551 // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
552 $additionalWhere = isset($this->mconf
['additionalWhere']) ?
$this->mconf
['additionalWhere'] : '';
553 if (isset($this->mconf
['additionalWhere.'])) {
554 $additionalWhere = $this->parent_cObj
->stdWrap($additionalWhere, $this->mconf
['additionalWhere.']);
557 // ... only for the FIRST level of a HMENU
558 if ($this->menuNumber
== 1 && $this->conf
['special']) {
559 $value = isset($this->conf
['special.']['value.'])
560 ?
$this->parent_cObj
->stdWrap($this->conf
['special.']['value'], $this->conf
['special.']['value.'])
561 : $this->conf
['special.']['value'];
562 switch ($this->conf
['special']) {
564 $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
567 $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
570 $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
573 $menuItems = $this->prepareMenuItemsForListMenu($value);
576 $menuItems = $this->prepareMenuItemsForUpdatedMenu(
578 $this->mconf
['alternativeSortingField'] ?
: false
582 $menuItems = $this->prepareMenuItemsForKeywordsMenu(
584 $this->mconf
['alternativeSortingField'] ?
: false
588 /** @var CategoryMenuUtility $categoryMenuUtility */
589 $categoryMenuUtility = GeneralUtility
::makeInstance(CategoryMenuUtility
::class);
590 $menuItems = $categoryMenuUtility->collectPages($value, $this->conf
['special.'], $this);
593 $menuItems = $this->prepareMenuItemsForRootlineMenu();
596 $menuItems = $this->prepareMenuitemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
599 if ($this->mconf
['sectionIndex']) {
600 $sectionIndexes = array();
601 foreach ($menuItems as $page) {
602 $sectionIndexes = $sectionIndexes +
$this->sectionIndex($alternativeSortingField, $page['uid']);
604 $menuItems = $sectionIndexes;
606 } elseif (is_array($this->alternativeMenuTempArray
)) {
607 // Setting $menuItems array if not level 1.
608 $menuItems = $this->alternativeMenuTempArray
;
609 } elseif ($this->mconf
['sectionIndex']) {
610 $menuItems = $this->sectionIndex($alternativeSortingField);
612 // Default: Gets a hierarchical menu based on subpages of $this->id
613 $menuItems = $this->sys_page
->getMenu($this->id
, '*', $alternativeSortingField, $additionalWhere);
619 * Fetches all menuitems if special = userfunction is set
621 * @param string $specialValue The value from special.value
622 * @param string $sortingField The sorting field
625 protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
627 $menuItems = $this->parent_cObj
->callUserFunction(
628 $this->conf
['special.']['userFunc'],
629 array_merge($this->conf
['special.'], array('value' => $specialValue, '_altSortField' => $sortingField)),
632 if (!is_array($menuItems)) {
633 $menuItems = array();
639 * Fetches all menuitems if special = language is set
641 * @param string $specialValue The value from special.value
644 protected function prepareMenuItemsForLanguageMenu($specialValue)
646 $menuItems = array();
647 // Getting current page record NOT overlaid by any translation:
648 $tsfe = $this->getTypoScriptFrontendController();
649 $currentPageWithNoOverlay = $this->sys_page
->getRawRecord('pages', $tsfe->page
['uid']);
650 // Traverse languages set up:
651 $languageItems = GeneralUtility
::intExplode(',', $specialValue);
652 foreach ($languageItems as $sUid) {
653 // Find overlay record:
655 $lRecs = $this->sys_page
->getPageOverlay($tsfe->page
['uid'], $sUid);
659 // Checking if the "disabled" state should be set.
660 if (GeneralUtility
::hideIfNotTranslated($tsfe->page
['l18n_cfg']) && $sUid &&
661 empty($lRecs) || GeneralUtility
::hideIfDefaultLanguage($tsfe->page
['l18n_cfg']) &&
662 (!$sUid ||
empty($lRecs)) ||
663 !$this->conf
['special.']['normalWhenNoLanguage'] && $sUid && empty($lRecs)
665 $iState = $tsfe->sys_language_uid
== $sUid ?
'USERDEF2' : 'USERDEF1';
667 $iState = $tsfe->sys_language_uid
== $sUid ?
'ACT' : 'NO';
669 if ($this->conf
['addQueryString']) {
670 $getVars = $this->parent_cObj
->getQueryArguments(
671 $this->conf
['addQueryString.'],
675 $this->analyzeCacheHashRequirements($getVars);
677 $getVars = '&L=' . $sUid;
680 $menuItems[] = array_merge(
681 array_merge($currentPageWithNoOverlay, $lRecs),
683 'ITEM_STATE' => $iState,
684 '_ADD_GETVARS' => $getVars,
693 * Fetches all menuitems if special = directory is set
695 * @param string $specialValue The value from special.value
696 * @param string $sortingField The sorting field
699 protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
701 $tsfe = $this->getTypoScriptFrontendController();
702 $databaseConnection = $this->getDatabaseConnection();
703 $menuItems = array();
704 if ($specialValue == '') {
705 $specialValue = $tsfe->page
['uid'];
707 $items = GeneralUtility
::intExplode(',', $specialValue);
708 foreach ($items as $id) {
709 $MP = $this->tmpl
->getFromMPmap($id);
710 // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
711 $mount_info = $this->sys_page
->getMountPointInfo($id);
712 if (is_array($mount_info)) {
713 if ($mount_info['overlay']) {
714 // Overlays should already have their full MPvars calculated:
715 $MP = $this->tmpl
->getFromMPmap($mount_info['mount_pid']);
716 $MP = $MP ?
$MP : $mount_info['MPvar'];
718 $MP = ($MP ?
$MP . ',' : '') . $mount_info['MPvar'];
720 $id = $mount_info['mount_pid'];
723 $res = $this->parent_cObj
->exec_getQuery('pages', array('pidInList' => $id, 'orderBy' => $sortingField));
724 while ($row = $databaseConnection->sql_fetch_assoc($res)) {
725 $tsfe->sys_page
->versionOL('pages', $row, true);
728 $mount_info = $this->sys_page
->getMountPointInfo($row['uid'], $row);
729 // There is a valid mount point.
730 if (is_array($mount_info) && $mount_info['overlay']) {
731 // Using "getPage" is OK since we need the check for enableFields
732 // AND for type 2 of mount pids we DO require a doktype < 200!
733 $mp_row = $this->sys_page
->getPage($mount_info['mount_pid']);
734 if (!empty($mp_row)) {
736 $row['_MP_PARAM'] = $mount_info['MPvar'];
738 // If the mount point could not be fetched with respect
739 // to enableFields, unset the row so it does not become a part of the menu!
743 // Add external MP params, then the row:
746 $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ?
',' . $row['_MP_PARAM'] : '');
748 $menuItems[$row['uid']] = $this->sys_page
->getPageOverlay($row);
752 $databaseConnection->sql_free_result($res);
758 * Fetches all menuitems if special = list is set
760 * @param string $specialValue The value from special.value
763 protected function prepareMenuItemsForListMenu($specialValue)
765 $menuItems = array();
766 if ($specialValue == '') {
767 $specialValue = $this->id
;
769 $skippedEnableFields = array();
770 if (!empty($this->mconf
['showAccessRestrictedPages'])) {
771 $skippedEnableFields = array('fe_group' => 1);
773 /** @var RelationHandler $loadDB*/
774 $loadDB = GeneralUtility
::makeInstance(RelationHandler
::class);
775 $loadDB->setFetchAllFields(true);
776 $loadDB->start($specialValue, 'pages');
777 $loadDB->additionalWhere
['pages'] = $this->parent_cObj
->enableFields('pages', false, $skippedEnableFields);
778 $loadDB->getFromDB();
779 foreach ($loadDB->itemArray
as $val) {
780 $MP = $this->tmpl
->getFromMPmap($val['id']);
782 $mount_info = $this->sys_page
->getMountPointInfo($val['id']);
783 // There is a valid mount point.
784 if (is_array($mount_info) && $mount_info['overlay']) {
785 // Using "getPage" is OK since we need the check for enableFields
786 // AND for type 2 of mount pids we DO require a doktype < 200!
787 $mp_row = $this->sys_page
->getPage($mount_info['mount_pid']);
788 if (!empty($mp_row)) {
790 $row['_MP_PARAM'] = $mount_info['MPvar'];
791 // Overlays should already have their full MPvars calculated
792 if ($mount_info['overlay']) {
793 $MP = $this->tmpl
->getFromMPmap($mount_info['mount_pid']);
795 unset($row['_MP_PARAM']);
799 // If the mount point could not be fetched with respect to
800 // enableFields, unset the row so it does not become a part of the menu!
804 $row = $loadDB->results
['pages'][$val['id']];
806 // Add versioning overlay for current page (to respect workspaces)
807 if (isset($row) && is_array($row)) {
808 $this->sys_page
->versionOL('pages', $row, true);
810 // Add external MP params, then the row:
811 if (isset($row) && is_array($row)) {
813 $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ?
',' . $row['_MP_PARAM'] : '');
815 $menuItems[] = $this->sys_page
->getPageOverlay($row);
822 * Fetches all menuitems if special = updated is set
824 * @param string $specialValue The value from special.value
825 * @param string $sortingField The sorting field
828 protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
830 $tsfe = $this->getTypoScriptFrontendController();
831 $menuItems = array();
832 if ($specialValue == '') {
833 $specialValue = $tsfe->page
['uid'];
835 $items = GeneralUtility
::intExplode(',', $specialValue);
836 if (MathUtility
::canBeInterpretedAsInteger($this->conf
['special.']['depth'])) {
837 $depth = MathUtility
::forceIntegerInRange($this->conf
['special.']['depth'], 1, 20);
841 // Max number of items
842 $limit = MathUtility
::forceIntegerInRange($this->conf
['special.']['limit'], 0, 100);
843 $maxAge = (int)$this->parent_cObj
->calc($this->conf
['special.']['maxAge']);
847 // *'auto', 'manual', 'tstamp'
848 $mode = $this->conf
['special.']['mode'];
850 $id_list_arr = array();
851 foreach ($items as $id) {
852 $bA = MathUtility
::forceIntegerInRange($this->conf
['special.']['beginAtLevel'], 0, 100);
853 $id_list_arr[] = $this->parent_cObj
->getTreeList(-1 * $id, $depth - 1 +
$bA, $bA - 1);
855 $id_list = implode(',', $id_list_arr);
856 // Get sortField (mode)
859 $sortField = 'starttime';
863 $sortField = 'lastUpdated';
866 $sortField = 'tstamp';
869 $sortField = 'crdate';
872 $sortField = 'SYS_LASTCHANGED';
874 $extraWhere = ($this->conf
['includeNotInMenu'] ?
'' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
875 if ($this->conf
['special.']['excludeNoSearchPages']) {
876 $extraWhere .= ' AND pages.no_search=0';
879 $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
881 $res = $this->parent_cObj
->exec_getQuery('pages', array(
883 'uidInList' => $id_list,
884 'where' => $sortField . '>=0' . $extraWhere,
885 'orderBy' => $sortingField ?
: $sortField . ' DESC',
888 while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($res)) {
889 $tsfe->sys_page
->versionOL('pages', $row, true);
890 if (is_array($row)) {
891 $menuItems[$row['uid']] = $this->sys_page
->getPageOverlay($row);
894 $this->getDatabaseConnection()->sql_free_result($res);
899 * Fetches all menuitems if special = keywords is set
901 * @param string $specialValue The value from special.value
902 * @param string $sortingField The sorting field
905 protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
907 $tsfe = $this->getTypoScriptFrontendController();
908 $menuItems = array();
909 list($specialValue) = GeneralUtility
::intExplode(',', $specialValue);
910 if (!$specialValue) {
911 $specialValue = $tsfe->page
['uid'];
913 if ($this->conf
['special.']['setKeywords'] ||
$this->conf
['special.']['setKeywords.']) {
914 $kw = isset($this->conf
['special.']['setKeywords.']) ?
$this->parent_cObj
->stdWrap($this->conf
['special.']['setKeywords'], $this->conf
['special.']['setKeywords.']) : $this->conf
['special.']['setKeywords'];
916 // The page record of the 'value'.
917 $value_rec = $this->sys_page
->getPage($specialValue);
918 $kfieldSrc = $this->conf
['special.']['keywordsField.']['sourceField'] ?
$this->conf
['special.']['keywordsField.']['sourceField'] : 'keywords';
920 $kw = trim($this->parent_cObj
->keywords($value_rec[$kfieldSrc]));
922 // *'auto', 'manual', 'tstamp'
923 $mode = $this->conf
['special.']['mode'];
926 $sortField = 'starttime';
930 $sortField = 'lastUpdated';
933 $sortField = 'tstamp';
936 $sortField = 'crdate';
939 $sortField = 'SYS_LASTCHANGED';
941 // Depth, limit, extra where
942 if (MathUtility
::canBeInterpretedAsInteger($this->conf
['special.']['depth'])) {
943 $depth = MathUtility
::forceIntegerInRange($this->conf
['special.']['depth'], 0, 20);
947 // Max number of items
948 $limit = MathUtility
::forceIntegerInRange($this->conf
['special.']['limit'], 0, 100);
949 $extraWhere = ' AND pages.uid<>' . $specialValue . ($this->conf
['includeNotInMenu'] ?
'' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
950 if ($this->conf
['special.']['excludeNoSearchPages']) {
951 $extraWhere .= ' AND pages.no_search=0';
954 $eLevel = $this->parent_cObj
->getKey(isset($this->conf
['special.']['entryLevel.'])
955 ?
$this->parent_cObj
->stdWrap($this->conf
['special.']['entryLevel'], $this->conf
['special.']['entryLevel.'])
956 : $this->conf
['special.']['entryLevel'], $this->tmpl
->rootLine
958 $startUid = (int)$this->tmpl
->rootLine
[$eLevel]['uid'];
959 // Which field is for keywords
960 $kfield = 'keywords';
961 if ($this->conf
['special.']['keywordsField']) {
962 list($kfield) = explode(' ', trim($this->conf
['special.']['keywordsField']));
964 // If there are keywords and the startuid is present
965 if ($kw && $startUid) {
966 $bA = MathUtility
::forceIntegerInRange($this->conf
['special.']['beginAtLevel'], 0, 100);
967 $id_list = $this->parent_cObj
->getTreeList(-1 * $startUid, $depth - 1 +
$bA, $bA - 1);
968 $kwArr = explode(',', $kw);
969 $keyWordsWhereArr = array();
970 foreach ($kwArr as $word) {
973 $keyWordsWhereArr[] = $kfield . ' LIKE \'%' . $this->getDatabaseConnection()->quoteStr($word, 'pages') . '%\'';
976 $where = empty($keyWordsWhereArr) ?
'' : '(' . implode(' OR ', $keyWordsWhereArr) . ')';
977 $res = $this->parent_cObj
->exec_getQuery('pages', array(
979 'uidInList' => $id_list,
980 'where' => $where . $extraWhere,
981 'orderBy' => $sortingField ?
: $sortField . ' desc',
984 while (($row = $this->getDatabaseConnection()->sql_fetch_assoc($res))) {
985 $tsfe->sys_page
->versionOL('pages', $row, true);
986 if (is_array($row)) {
987 $menuItems[$row['uid']] = $this->sys_page
->getPageOverlay($row);
990 $this->getDatabaseConnection()->sql_free_result($res);
996 * Fetches all menuitems if special = rootline is set
1000 protected function prepareMenuItemsForRootlineMenu()
1002 $menuItems = array();
1003 $range = isset($this->conf
['special.']['range.'])
1004 ?
$this->parent_cObj
->stdWrap($this->conf
['special.']['range'], $this->conf
['special.']['range.'])
1005 : $this->conf
['special.']['range'];
1006 $begin_end = explode('|', $range);
1007 $begin_end[0] = (int)$begin_end[0];
1008 if (!MathUtility
::canBeInterpretedAsInteger($begin_end[1])) {
1011 $beginKey = $this->parent_cObj
->getKey($begin_end[0], $this->tmpl
->rootLine
);
1012 $endKey = $this->parent_cObj
->getKey($begin_end[1], $this->tmpl
->rootLine
);
1013 if ($endKey < $beginKey) {
1014 $endKey = $beginKey;
1016 $rl_MParray = array();
1017 foreach ($this->tmpl
->rootLine
as $k_rl => $v_rl) {
1018 // For overlaid mount points, set the variable right now:
1019 if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1020 $rl_MParray[] = $v_rl['_MP_PARAM'];
1022 // Traverse rootline:
1023 if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1025 $menuItems[$temp_key] = $this->sys_page
->getPage($v_rl['uid']);
1026 if (!empty($menuItems[$temp_key])) {
1027 // If there are no specific target for the page, put the level specific target on.
1028 if (!$menuItems[$temp_key]['target']) {
1029 $menuItems[$temp_key]['target'] = $this->conf
['special.']['targets.'][$k_rl];
1030 $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1033 unset($menuItems[$temp_key]);
1036 // For normal mount points, set the variable for next level.
1037 if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1038 $rl_MParray[] = $v_rl['_MP_PARAM'];
1041 // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1042 if (isset($this->conf
['special.']['reverseOrder']) && $this->conf
['special.']['reverseOrder']) {
1043 $menuItems = array_reverse($menuItems);
1049 * Fetches all menuitems if special = browse is set
1051 * @param string $specialValue The value from special.value
1052 * @param string $sortingField The sorting field
1053 * @param string $additionalWhere Additional WHERE clause
1056 protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1058 $menuItems = array();
1059 list($specialValue) = GeneralUtility
::intExplode(',', $specialValue);
1060 if (!$specialValue) {
1061 $specialValue = $this->getTypoScriptFrontendController()->page
['uid'];
1063 // Will not work out of rootline
1064 if ($specialValue != $this->tmpl
->rootLine
[0]['uid']) {
1066 // The page record of the 'value'.
1067 $value_rec = $this->sys_page
->getPage($specialValue);
1068 // 'up' page cannot be outside rootline
1069 if ($value_rec['pid']) {
1070 // The page record of 'up'.
1071 $recArr['up'] = $this->sys_page
->getPage($value_rec['pid']);
1073 // If the 'up' item was NOT level 0 in rootline...
1074 if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl
->rootLine
[0]['uid']) {
1075 // The page record of "index".
1076 $recArr['index'] = $this->sys_page
->getPage($recArr['up']['pid']);
1078 // check if certain pages should be excluded
1079 $additionalWhere .= ($this->conf
['includeNotInMenu'] ?
'' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1080 if ($this->conf
['special.']['excludeNoSearchPages']) {
1081 $additionalWhere .= ' AND pages.no_search=0';
1083 // prev / next is found
1084 $prevnext_menu = $this->removeInaccessiblePages($this->sys_page
->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1087 foreach ($prevnext_menu as $k_b => $v_b) {
1089 $recArr['next'] = $v_b;
1092 if ($v_b['uid'] == $specialValue) {
1094 $recArr['prev'] = $prevnext_menu[$lastKey];
1101 $recArr['first'] = reset($prevnext_menu);
1102 $recArr['last'] = end($prevnext_menu);
1103 // prevsection / nextsection is found
1104 // You can only do this, if there is a valid page two levels up!
1105 if (!empty($recArr['index']['uid'])) {
1106 $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page
->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1109 foreach ($prevnextsection_menu as $k_b => $v_b) {
1111 $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page
->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1112 if (!empty($sectionRec_temp)) {
1113 $recArr['nextsection'] = reset($sectionRec_temp);
1114 $recArr['nextsection_last'] = end($sectionRec_temp);
1118 if ($v_b['uid'] == $value_rec['pid']) {
1120 $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page
->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1121 if (!empty($sectionRec_temp)) {
1122 $recArr['prevsection'] = reset($sectionRec_temp);
1123 $recArr['prevsection_last'] = end($sectionRec_temp);
1131 if ($this->conf
['special.']['items.']['prevnextToSection']) {
1132 if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1133 $recArr['prev'] = $recArr['prevsection_last'];
1135 if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1136 $recArr['next'] = $recArr['nextsection'];
1139 $items = explode('|', $this->conf
['special.']['items']);
1141 foreach ($items as $k_b => $v_b) {
1142 $v_b = strtolower(trim($v_b));
1143 if ((int)$this->conf
['special.'][$v_b . '.']['uid']) {
1144 $recArr[$v_b] = $this->sys_page
->getPage((int)$this->conf
['special.'][$v_b . '.']['uid']);
1146 if (is_array($recArr[$v_b])) {
1147 $menuItems[$c] = $recArr[$v_b];
1148 if ($this->conf
['special.'][$v_b . '.']['target']) {
1149 $menuItems[$c]['target'] = $this->conf
['special.'][$v_b . '.']['target'];
1151 $tmpSpecialFields = $this->conf
['special.'][$v_b . '.']['fields.'];
1152 if (is_array($tmpSpecialFields)) {
1153 foreach ($tmpSpecialFields as $fk => $val) {
1154 $menuItems[$c][$fk] = $val;
1165 * Analyzes the parameters to find if the link needs a cHash parameter.
1167 * @param string $queryString
1170 protected function analyzeCacheHashRequirements($queryString)
1172 $parameters = GeneralUtility
::explodeUrl2Array($queryString);
1173 if (!empty($parameters)) {
1174 /** @var CacheHashCalculator $cacheHashCalculator */
1175 $cacheHashCalculator = GeneralUtility
::makeInstance(CacheHashCalculator
::class);
1176 $cHashParameters = $cacheHashCalculator->getRelevantParameters($queryString);
1177 if (count($cHashParameters) > 1) {
1178 $this->useCacheHash
= (
1179 $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ||
1180 !isset($parameters['no_cache']) ||
1181 !$parameters['no_cache']
1188 * Checks if a page is OK to include in the final menu item array. Pages can be excluded if the doktype is wrong,
1189 * if they are hidden in navigation, have a uid in the list of banned uids etc.
1191 * @param array $data Array of menu items
1192 * @param array $banUidArray Array of page uids which are to be excluded
1193 * @param bool $spacer If set, then the page is a spacer.
1194 * @return bool Returns TRUE if the page can be safely included.
1196 * @throws \UnexpectedValueException
1198 public function filterMenuPages(&$data, $banUidArray, $spacer)
1200 $includePage = true;
1201 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'])) {
1202 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] as $classRef) {
1203 $hookObject = GeneralUtility
::getUserObj($classRef);
1204 if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface
) {
1205 throw new \
UnexpectedValueException($classRef . ' must implement interface ' . AbstractMenuFilterPagesHookInterface
::class, 1269877402);
1207 $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $spacer, $this);
1210 if (!$includePage) {
1213 if ($data['_SAFE']) {
1218 ($this->mconf
['SPC'] ||
!$spacer) // If the spacer-function is not enabled, spacers will not enter the $menuArr
1219 && (!$data['nav_hide'] ||
$this->conf
['includeNotInMenu']) // Not hidden in navigation
1220 && !GeneralUtility
::inList($this->doktypeExcludeList
, $data['doktype']) // Page may not be 'not_in_menu' or 'Backend User Section'
1221 && !ArrayUtility
::inArray($banUidArray, $data['uid']) // not in banned uid's
1223 // Checks if the default language version can be shown:
1224 // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
1225 $tsfe = $this->getTypoScriptFrontendController();
1226 $blockPage = GeneralUtility
::hideIfDefaultLanguage($data['l18n_cfg']) && (!$tsfe->sys_language_uid ||
$tsfe->sys_language_uid
&& !$data['_PAGES_OVERLAY']);
1228 // Checking if a page should be shown in the menu depending on whether a translation exists:
1230 // There is an alternative language active AND the current page requires a translation:
1231 if ($tsfe->sys_language_uid
&& GeneralUtility
::hideIfNotTranslated($data['l18n_cfg'])) {
1232 if (!$data['_PAGES_OVERLAY']) {
1236 // Continue if token is TRUE:
1238 // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1239 if ($this->conf
['protectLvar']) {
1240 $languageUid = (int)$tsfe->config
['config']['sys_language_uid'];
1241 if ($languageUid && ($this->conf
['protectLvar'] == 'all' || GeneralUtility
::hideIfNotTranslated($data['l18n_cfg']))) {
1242 $olRec = $tsfe->sys_page
->getPageOverlay($data['uid'], $languageUid);
1243 if (empty($olRec)) {
1244 // If no pages_language_overlay record then page can NOT be accessed in
1245 // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1246 $data['_ADD_GETVARS'] .= '&L=0';
1258 * Generating the per-menu-item configuration arrays based on the settings for item states (NO, RO, ACT, CUR etc)
1259 * set in ->mconf (config for the current menu object)
1260 * Basically it will produce an individual array for each menu item based on the item states.
1261 * BUT in addition the "optionSplit" syntax for the values is ALSO evaluated here so that all property-values
1262 * are "option-splitted" and the output will thus be resolved.
1263 * Is called from the "generate" functions in the extension classes. The function is processor intensive due to
1264 * the option split feature in particular. But since the generate function is not always called
1265 * (since the ->result array may be cached, see makeMenu) it doesn't hurt so badly.
1267 * @param int $splitCount Number of menu items in the menu
1268 * @return array An array with two keys: array($NOconf,$ROconf) - where $NOconf contains the resolved configuration for each item when NOT rolled-over and $ROconf contains the ditto for the mouseover state (if any)
1272 public function procesItemStates($splitCount)
1274 // Prepare normal settings
1275 if (!is_array($this->mconf
['NO.']) && $this->mconf
['NO']) {
1276 // Setting a blank array if NO=1 and there are no properties.
1277 $this->mconf
['NO.'] = array();
1279 $NOconf = $this->tmpl
->splitConfArray($this->mconf
['NO.'], $splitCount);
1280 // Prepare rollOver settings, overriding normal settings
1282 if ($this->mconf
['RO']) {
1283 $ROconf = $this->tmpl
->splitConfArray($this->mconf
['RO.'], $splitCount);
1285 // Prepare IFSUB settings, overriding normal settings
1286 // IFSUB is TRUE if there exist submenu items to the current item
1287 if (!empty($this->mconf
['IFSUB'])) {
1289 $IFSUBROconf = null;
1290 foreach ($NOconf as $key => $val) {
1291 if ($this->isItemState('IFSUB', $key)) {
1292 // if this is the first IFSUB element, we must generate IFSUB.
1293 if ($IFSUBconf === null) {
1294 $IFSUBconf = $this->tmpl
->splitConfArray($this->mconf
['IFSUB.'], $splitCount);
1295 if (!empty($this->mconf
['IFSUBRO'])) {
1296 $IFSUBROconf = $this->tmpl
->splitConfArray($this->mconf
['IFSUBRO.'], $splitCount);
1299 // Substitute normal with ifsub
1300 if (isset($IFSUBconf[$key])) {
1301 $NOconf[$key] = $IFSUBconf[$key];
1303 // If rollOver on normal, we must apply a state for rollOver on the active
1305 // If RollOver on active then apply this
1306 $ROconf[$key] = !empty($IFSUBROconf[$key]) ?
$IFSUBROconf[$key] : $IFSUBconf[$key];
1311 // Prepare active settings, overriding normal settings
1312 if (!empty($this->mconf
['ACT'])) {
1316 foreach ($NOconf as $key => $val) {
1317 if ($this->isItemState('ACT', $key)) {
1318 // If this is the first 'active', we must generate ACT.
1319 if ($ACTconf === null) {
1320 $ACTconf = $this->tmpl
->splitConfArray($this->mconf
['ACT.'], $splitCount);
1321 // Prepare active rollOver settings, overriding normal active settings
1322 if (!empty($this->mconf
['ACTRO'])) {
1323 $ACTROconf = $this->tmpl
->splitConfArray($this->mconf
['ACTRO.'], $splitCount);
1326 // Substitute normal with active
1327 if (isset($ACTconf[$key])) {
1328 $NOconf[$key] = $ACTconf[$key];
1330 // If rollOver on normal, we must apply a state for rollOver on the active
1332 // If RollOver on active then apply this
1333 $ROconf[$key] = !empty($ACTROconf[$key]) ?
$ACTROconf[$key] : $ACTconf[$key];
1338 // Prepare ACT (active)/IFSUB settings, overriding normal settings
1339 // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
1340 if (!empty($this->mconf
['ACTIFSUB'])) {
1341 $ACTIFSUBconf = null;
1342 $ACTIFSUBROconf = null;
1344 foreach ($NOconf as $key => $val) {
1345 if ($this->isItemState('ACTIFSUB', $key)) {
1346 // If this is the first 'active', we must generate ACTIFSUB.
1347 if ($ACTIFSUBconf === null) {
1348 $ACTIFSUBconf = $this->tmpl
->splitConfArray($this->mconf
['ACTIFSUB.'], $splitCount);
1349 // Prepare active rollOver settings, overriding normal active settings
1350 if (!empty($this->mconf
['ACTIFSUBRO'])) {
1351 $ACTIFSUBROconf = $this->tmpl
->splitConfArray($this->mconf
['ACTIFSUBRO.'], $splitCount);
1354 // Substitute normal with active
1355 if (isset($ACTIFSUBconf[$key])) {
1356 $NOconf[$key] = $ACTIFSUBconf[$key];
1358 // If rollOver on normal, we must apply a state for rollOver on the active
1360 // If RollOver on active then apply this
1361 $ROconf[$key] = !empty($ACTIFSUBROconf[$key]) ?
$ACTIFSUBROconf[$key] : $ACTIFSUBconf[$key];
1366 // Prepare CUR (current) settings, overriding normal settings
1367 // CUR is TRUE if the current page equals the item here!
1368 if (!empty($this->mconf
['CUR'])) {
1371 foreach ($NOconf as $key => $val) {
1372 if ($this->isItemState('CUR', $key)) {
1373 // if this is the first 'current', we must generate CUR. Basically this control is just inherited
1374 // from the other implementations as current would only exist one time and that's it
1375 // (unless you use special-features of HMENU)
1376 if ($CURconf === null) {
1377 $CURconf = $this->tmpl
->splitConfArray($this->mconf
['CUR.'], $splitCount);
1378 if (!empty($this->mconf
['CURRO'])) {
1379 $CURROconf = $this->tmpl
->splitConfArray($this->mconf
['CURRO.'], $splitCount);
1382 // Substitute normal with current
1383 if (isset($CURconf[$key])) {
1384 $NOconf[$key] = $CURconf[$key];
1386 // If rollOver on normal, we must apply a state for rollOver on the active
1388 // If RollOver on active then apply this
1389 $ROconf[$key] = !empty($CURROconf[$key]) ?
$CURROconf[$key] : $CURconf[$key];
1394 // Prepare CUR (current)/IFSUB settings, overriding normal settings
1395 // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
1396 if (!empty($this->mconf
['CURIFSUB'])) {
1397 $CURIFSUBconf = null;
1398 $CURIFSUBROconf = null;
1399 foreach ($NOconf as $key => $val) {
1400 if ($this->isItemState('CURIFSUB', $key)) {
1401 // If this is the first 'current', we must generate CURIFSUB.
1402 if ($CURIFSUBconf === null) {
1403 $CURIFSUBconf = $this->tmpl
->splitConfArray($this->mconf
['CURIFSUB.'], $splitCount);
1404 // Prepare current rollOver settings, overriding normal current settings
1405 if (!empty($this->mconf
['CURIFSUBRO'])) {
1406 $CURIFSUBROconf = $this->tmpl
->splitConfArray($this->mconf
['CURIFSUBRO.'], $splitCount);
1409 // Substitute normal with active
1410 if ($CURIFSUBconf[$key]) {
1411 $NOconf[$key] = $CURIFSUBconf[$key];
1413 // If rollOver on normal, we must apply a state for rollOver on the current
1415 // If RollOver on current then apply this
1416 $ROconf[$key] = !empty($CURIFSUBROconf[$key]) ?
$CURIFSUBROconf[$key] : $CURIFSUBconf[$key];
1421 // Prepare active settings, overriding normal settings
1422 if (!empty($this->mconf
['USR'])) {
1426 foreach ($NOconf as $key => $val) {
1427 if ($this->isItemState('USR', $key)) {
1428 // if this is the first active, we must generate USR.
1429 if ($USRconf === null) {
1430 $USRconf = $this->tmpl
->splitConfArray($this->mconf
['USR.'], $splitCount);
1431 // Prepare active rollOver settings, overriding normal active settings
1432 if (!empty($this->mconf
['USRRO'])) {
1433 $USRROconf = $this->tmpl
->splitConfArray($this->mconf
['USRRO.'], $splitCount);
1436 // Substitute normal with active
1437 if ($USRconf[$key]) {
1438 $NOconf[$key] = $USRconf[$key];
1440 // If rollOver on normal, we must apply a state for rollOver on the active
1442 // If RollOver on active then apply this
1443 $ROconf[$key] = !empty($USRROconf[$key]) ?
$USRROconf[$key] : $USRconf[$key];
1448 // Prepare spacer settings, overriding normal settings
1449 if (!empty($this->mconf
['SPC'])) {
1452 foreach ($NOconf as $key => $val) {
1453 if ($this->isItemState('SPC', $key)) {
1454 // If this is the first spacer, we must generate SPC.
1455 if ($SPCconf === null) {
1456 $SPCconf = $this->tmpl
->splitConfArray($this->mconf
['SPC.'], $splitCount);
1458 // Substitute normal with spacer
1459 if (isset($SPCconf[$key])) {
1460 $NOconf[$key] = $SPCconf[$key];
1465 // Prepare Userdefined settings
1466 if (!empty($this->mconf
['USERDEF1'])) {
1467 $USERDEF1conf = null;
1468 $USERDEF1ROconf = null;
1470 foreach ($NOconf as $key => $val) {
1471 if ($this->isItemState('USERDEF1', $key)) {
1472 // If this is the first active, we must generate USERDEF1.
1473 if ($USERDEF1conf === null) {
1474 $USERDEF1conf = $this->tmpl
->splitConfArray($this->mconf
['USERDEF1.'], $splitCount);
1475 // Prepare active rollOver settings, overriding normal active settings
1476 if (!empty($this->mconf
['USERDEF1RO'])) {
1477 $USERDEF1ROconf = $this->tmpl
->splitConfArray($this->mconf
['USERDEF1RO.'], $splitCount);
1480 // Substitute normal with active
1481 if (isset($USERDEF1conf[$key])) {
1482 $NOconf[$key] = $USERDEF1conf[$key];
1484 // If rollOver on normal, we must apply a state for rollOver on the active
1486 // If RollOver on active then apply this
1487 $ROconf[$key] = !empty($USERDEF1ROconf[$key]) ?
$USERDEF1ROconf[$key] : $USERDEF1conf[$key];
1492 // Prepare Userdefined settings
1493 if (!empty($this->mconf
['USERDEF2'])) {
1494 $USERDEF2conf = null;
1495 $USERDEF2ROconf = null;
1497 foreach ($NOconf as $key => $val) {
1498 if ($this->isItemState('USERDEF2', $key)) {
1499 // If this is the first active, we must generate USERDEF2.
1500 if ($USERDEF2conf) {
1501 $USERDEF2conf = $this->tmpl
->splitConfArray($this->mconf
['USERDEF2.'], $splitCount);
1502 // Prepare active rollOver settings, overriding normal active settings
1503 if (!empty($this->mconf
['USERDEF2RO'])) {
1504 $USERDEF2ROconf = $this->tmpl
->splitConfArray($this->mconf
['USERDEF2RO.'], $splitCount);
1507 // Substitute normal with active
1508 if (isset($USERDEF2conf[$key])) {
1509 $NOconf[$key] = $USERDEF2conf[$key];
1511 // If rollOver on normal, we must apply a state for rollOver on the active
1513 // If RollOver on active then apply this
1514 $ROconf[$key] = !empty($USERDEF2ROconf[$key]) ?
$USERDEF2ROconf[$key] : $USERDEF2conf[$key];
1519 return array($NOconf, $ROconf);
1523 * Creates the URL, target and onclick values for the menu item link. Returns them in an array as key/value pairs for <A>-tag attributes
1524 * This function doesn't care about the url, because if we let the url be redirected, it will be logged in the stat!!!
1526 * @param int $key Pointer to a key in the $this->menuArr array where the value for that key represents the menu item we are linking to (page record)
1527 * @param string $altTarget Alternative target
1528 * @param string $typeOverride Alternative type
1529 * @return array Returns an array with A-tag attributes as key/value pairs (HREF, TARGET and onClick)
1532 public function link($key, $altTarget = '', $typeOverride = '')
1535 $MP_var = $this->getMPvar($key);
1536 $MP_params = $MP_var ?
'&MP=' . rawurlencode($MP_var) : '';
1537 // Setting override ID
1538 if ($this->mconf
['overrideId'] ||
$this->menuArr
[$key]['overrideId']) {
1539 $overrideArray = array();
1540 // If a user script returned the value overrideId in the menu array we use that as page id
1541 $overrideArray['uid'] = $this->mconf
['overrideId'] ?
: $this->menuArr
[$key]['overrideId'];
1542 $overrideArray['alias'] = '';
1543 // Clear MP parameters since ID was changed.
1546 $overrideArray = '';
1548 // Setting main target:
1550 $mainTarget = $altTarget;
1551 } elseif ($this->mconf
['target.']) {
1552 $mainTarget = $this->parent_cObj
->stdWrap($this->mconf
['target'], $this->mconf
['target.']);
1554 $mainTarget = $this->mconf
['target'];
1557 $addParams = $this->mconf
['addParams'] . $MP_params;
1558 if ($this->mconf
['collapse'] && $this->isActive($this->menuArr
[$key]['uid'], $this->getMPvar($key))) {
1559 $thePage = $this->sys_page
->getPage($this->menuArr
[$key]['pid']);
1560 $addParams .= $this->menuArr
[$key]['_ADD_GETVARS'];
1561 $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1563 $addParams .= $this->I
['val']['additionalParams'] . $this->menuArr
[$key]['_ADD_GETVARS'];
1564 $LD = $this->menuTypoLink($this->menuArr
[$key], $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1566 // Override URL if using "External URL"
1567 if ($this->menuArr
[$key]['doktype'] == PageRepository
::DOKTYPE_LINK
) {
1568 if ($this->menuArr
[$key]['urltype'] == 3 && GeneralUtility
::validEmail($this->menuArr
[$key]['url'])) {
1569 // Create mailto-link using \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink (concerning spamProtectEmailAddresses):
1570 $LD['totalURL'] = $this->parent_cObj
->typoLink_URL(array('parameter' => $this->menuArr
[$key]['url']));
1573 $LD['totalURL'] = $this->parent_cObj
->typoLink_URL(array('parameter' => $this->getSysPage()->getExtURL($this->menuArr
[$key])));
1577 $tsfe = $this->getTypoScriptFrontendController();
1579 // Override url if current page is a shortcut
1581 if ($this->menuArr
[$key]['doktype'] == PageRepository
::DOKTYPE_SHORTCUT
&& $this->menuArr
[$key]['shortcut_mode'] != PageRepository
::SHORTCUT_MODE_RANDOM_SUBPAGE
) {
1582 $menuItem = $this->determineOriginalShortcutPage($this->menuArr
[$key]);
1584 $shortcut = $tsfe->getPageShortcut(
1585 $menuItem['shortcut'],
1586 $menuItem['shortcut_mode'],
1592 } catch (\Exception
$ex) {
1594 if (!is_array($shortcut)) {
1597 // Only setting url, not target
1598 $LD['totalURL'] = $this->parent_cObj
->typoLink_URL(array(
1599 'parameter' => $shortcut['uid'],
1600 'additionalParams' => $addParams . $this->I
['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1601 'linkAccessRestrictedPages' => $this->mconf
['showAccessRestrictedPages'] && $this->mconf
['showAccessRestrictedPages'] !== 'NONE'
1605 $pageData = $shortcut;
1606 $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr
[$key]['uid'];
1608 $pageData = $this->menuArr
[$key];
1610 // Manipulation in case of access restricted pages:
1611 $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1612 // Overriding URL / Target if set to do so:
1613 if ($this->menuArr
[$key]['_OVERRIDE_HREF']) {
1614 $LD['totalURL'] = $this->menuArr
[$key]['_OVERRIDE_HREF'];
1615 if ($this->menuArr
[$key]['_OVERRIDE_TARGET']) {
1616 $LD['target'] = $this->menuArr
[$key]['_OVERRIDE_TARGET'];
1619 // OnClick open in windows.
1621 if ($this->mconf
['JSWindow']) {
1622 $conf = $this->mconf
['JSWindow.'];
1623 $url = $LD['totalURL'];
1624 $LD['totalURL'] = '#';
1625 $onClick = 'openPic('
1626 . GeneralUtility
::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1627 . '\'' . ($conf['newWindow'] ?
md5($url) : 'theNewPage') . '\','
1628 . GeneralUtility
::quoteJSvalue($conf['params']) . '); return false;';
1629 $tsfe->setJS('openPic');
1631 // look for type and popup
1632 // following settings are valid in field target:
1633 // 230 will add type=230 to the link
1634 // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1635 // 230 _blank will add type=230 to the link and open with target "_blank"
1636 // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1638 $targetIsType = $LD['target'] && MathUtility
::canBeInterpretedAsInteger($LD['target']) ?
(int)$LD['target'] : false;
1639 if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) ||
$targetIsType) {
1641 if ((int)$matches[1] ||
$targetIsType) {
1642 $LD['totalURL'] = $this->parent_cObj
->URLqMark($LD['totalURL'], '&type=' . ($targetIsType ?
: (int)$matches[1]));
1643 $LD['target'] = $targetIsType ?
'' : trim(substr($LD['target'], strlen($matches[1]) +
1));
1645 // Open in popup window?
1646 if ($matches[3] && $matches[4]) {
1647 $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ?
',' . substr($matches[5], 1) : '');
1648 $onClick = 'vHWin=window.open('
1649 . GeneralUtility
::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1650 . ',\'FEopenLink\',' . GeneralUtility
::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1656 // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1657 // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1658 // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1659 $list['HREF'] = (string)$LD['totalURL'] !== '' ?
$LD['totalURL'] : $tsfe->baseUrl
;
1660 $list['TARGET'] = $LD['target'];
1661 $list['onClick'] = $onClick;
1666 * Determines original shortcut destination in page overlays.
1668 * Since the pages records used for menu rendering are overlaid by default,
1669 * the original 'shortcut' value is lost, if a translation did not define one.
1670 * The behaviour in TSFE can be compared to the 'mergeIfNotBlank' feature, but
1671 * it's hardcoded there and not related to the mentioned setting at all.
1673 * @param array $page
1676 protected function determineOriginalShortcutPage(array $page)
1678 // Check if modification is required
1680 $this->getTypoScriptFrontendController()->sys_language_uid
> 0
1681 && empty($page['shortcut'])
1682 && !empty($page['uid'])
1683 && !empty($page['_PAGES_OVERLAY'])
1684 && !empty($page['_PAGES_OVERLAY_UID'])
1686 // Using raw record since the record was overlaid and is correct already:
1687 $originalPage = $this->sys_page
->getRawRecord('pages', $page['uid']);
1689 if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1690 $page['shortcut'] = $originalPage['shortcut'];
1698 * Will change $LD (passed by reference) if the page is access restricted
1700 * @param array $LD The array from the linkData() function
1701 * @param array $page Page array
1702 * @param string $mainTarget Main target value
1703 * @param string $typeOverride Type number override if any
1704 * @return void ($LD passed by reference might be changed.)
1706 public function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1708 // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1709 if ($this->mconf
['showAccessRestrictedPages'] && $this->mconf
['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1710 $thePage = $this->sys_page
->getPage($this->mconf
['showAccessRestrictedPages']);
1711 $addParams = str_replace(
1717 rawurlencode($LD['totalURL']),
1718 isset($page['_SHORTCUT_PAGE_UID']) ?
$page['_SHORTCUT_PAGE_UID'] : $page['uid']
1720 $this->mconf
['showAccessRestrictedPages.']['addParams']
1722 $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', '', $addParams, $typeOverride);
1727 * Creates a submenu level to the current level - if configured for.
1729 * @param int $uid Page id of the current page for which a submenu MAY be produced (if conditions are met)
1730 * @param string $objSuffix Object prefix, see ->start()
1731 * @return string HTML content of the submenu
1734 public function subMenu($uid, $objSuffix = '')
1736 // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1738 if (is_array($this->menuArr
[$this->I
['key']]['_SUB_MENU']) && !empty($this->menuArr
[$this->I
['key']]['_SUB_MENU'])) {
1739 $altArray = $this->menuArr
[$this->I
['key']]['_SUB_MENU'];
1741 // Make submenu if the page is the next active
1742 $menuType = $this->conf
[($this->menuNumber +
1) . $objSuffix];
1743 // stdWrap for expAll
1744 if (isset($this->mconf
['expAll.'])) {
1745 $this->mconf
['expAll'] = $this->parent_cObj
->stdWrap($this->mconf
['expAll'], $this->mconf
['expAll.']);
1747 if (($this->mconf
['expAll'] ||
$this->isNext($uid, $this->getMPvar($this->I
['key'])) ||
is_array($altArray)) && !$this->mconf
['sectionIndex']) {
1749 $menuObjectFactory = GeneralUtility
::makeInstance(MenuContentObjectFactory
::class);
1750 /** @var $submenu AbstractMenuContentObject */
1751 $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1752 $submenu->entryLevel
= $this->entryLevel +
1;
1753 $submenu->rL_uidRegister
= $this->rL_uidRegister
;
1754 $submenu->MP_array
= $this->MP_array
;
1755 if ($this->menuArr
[$this->I
['key']]['_MP_PARAM']) {
1756 $submenu->MP_array
[] = $this->menuArr
[$this->I
['key']]['_MP_PARAM'];
1758 // Especially scripts that build the submenu needs the parent data
1759 $submenu->parent_cObj
= $this->parent_cObj
;
1760 // Setting alternativeMenuTempArray (will be effective only if an array)
1761 if (is_array($altArray)) {
1762 $submenu->alternativeMenuTempArray
= $altArray;
1764 if ($submenu->start($this->tmpl
, $this->sys_page
, $uid, $this->conf
, $this->menuNumber +
1, $objSuffix)) {
1765 $submenu->makeMenu();
1766 // Memorize the current menu item count
1767 $tsfe = $this->getTypoScriptFrontendController();
1768 $tempCountMenuObj = $tsfe->register
['count_MENUOBJ'];
1769 // Reset the menu item count for the submenu
1770 $tsfe->register
['count_MENUOBJ'] = 0;
1771 $content = $submenu->writeMenu();
1772 // Restore the item count now that the submenu has been handled
1773 $tsfe->register
['count_MENUOBJ'] = $tempCountMenuObj;
1774 $tsfe->register
['count_menuItems'] = count($this->menuArr
);
1777 } catch (Exception\NoSuchMenuTypeException
$e) {
1784 * Returns TRUE if the page with UID $uid is the NEXT page in root line (which means a submenu should be drawn)
1786 * @param int $uid Page uid to evaluate.
1787 * @param string $MPvar MPvar for the current position of item.
1788 * @return bool TRUE if page with $uid is active
1792 public function isNext($uid, $MPvar = '')
1794 // Check for always active PIDs:
1795 if (!empty($this->alwaysActivePIDlist
) && in_array((int)$uid, $this->alwaysActivePIDlist
, true)) {
1798 $testUid = $uid . ($MPvar ?
':' . $MPvar : '');
1799 if ($uid && $testUid == $this->nextActive
) {
1806 * Returns TRUE if the page with UID $uid is active (in the current rootline)
1808 * @param int $uid Page uid to evaluate.
1809 * @param string $MPvar MPvar for the current position of item.
1810 * @return bool TRUE if page with $uid is active
1813 public function isActive($uid, $MPvar = '')
1815 // Check for always active PIDs:
1816 if (!empty($this->alwaysActivePIDlist
) && in_array((int)$uid, $this->alwaysActivePIDlist
, true)) {
1819 $testUid = $uid . ($MPvar ?
':' . $MPvar : '');
1820 if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister
, true)) {
1827 * Returns TRUE if the page with UID $uid is the CURRENT page (equals $this->getTypoScriptFrontendController()->id)
1829 * @param int $uid Page uid to evaluate.
1830 * @param string $MPvar MPvar for the current position of item.
1831 * @return bool TRUE if page $uid = $this->getTypoScriptFrontendController()->id
1834 public function isCurrent($uid, $MPvar = '')
1836 $testUid = $uid . ($MPvar ?
':' . $MPvar : '');
1837 return $uid && end($this->rL_uidRegister
) === 'ITEM:' . $testUid;
1841 * Returns TRUE if there is a submenu with items for the page id, $uid
1842 * Used by the item states "IFSUB", "ACTIFSUB" and "CURIFSUB" to check if there is a submenu
1844 * @param int $uid Page uid for which to search for a submenu
1845 * @return bool Returns TRUE if there was a submenu with items found
1848 public function isSubMenu($uid)
1850 // Looking for a mount-pid for this UID since if that
1851 // exists we should look for a subpages THERE and not in the input $uid;
1852 $mount_info = $this->sys_page
->getMountPointInfo($uid);
1853 if (is_array($mount_info)) {
1854 $uid = $mount_info['mount_pid'];
1856 $recs = $this->sys_page
->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1857 $hasSubPages = false;
1858 $bannedUids = $this->getBannedUids();
1859 foreach ($recs as $theRec) {
1860 // no valid subpage if the document type is excluded from the menu
1861 if (GeneralUtility
::inList($this->doktypeExcludeList
, $theRec['doktype'])) {
1864 // No valid subpage if the page is hidden inside menus and
1865 // it wasn't forced to show such entries
1866 if ($theRec['nav_hide'] && !$this->conf
['includeNotInMenu']) {
1869 // No valid subpage if the default language should be shown and the page settings
1870 // are excluding the visibility of the default language
1871 if (!$this->getTypoScriptFrontendController()->sys_language_uid
&& GeneralUtility
::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
1874 // No valid subpage if the alternative language should be shown and the page settings
1875 // are requiring a valid overlay but it doesn't exists
1876 $hideIfNotTranslated = GeneralUtility
::hideIfNotTranslated($theRec['l18n_cfg']);
1877 if ($this->getTypoScriptFrontendController()->sys_language_uid
&& $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1880 // No valid subpage if the subpage is banned by excludeUidList
1881 if (in_array($theRec['uid'], $bannedUids)) {
1884 $hasSubPages = true;
1887 return $hasSubPages;
1891 * Used by procesItemStates() to evaluate if a menu item (identified by $key) is in a certain state.
1893 * @param string $kind The item state to evaluate (SPC, IFSUB, ACT etc... but no xxxRO states of course)
1894 * @param int $key Key pointing to menu item from ->menuArr
1895 * @return bool Returns TRUE if state matches
1897 * @see procesItemStates()
1899 public function isItemState($kind, $key)
1902 // If any value is set for ITEM_STATE the normal evaluation is discarded
1903 if ($this->menuArr
[$key]['ITEM_STATE']) {
1904 if ((string)$this->menuArr
[$key]['ITEM_STATE'] === (string)$kind) {
1910 $natVal = (bool)$this->menuArr
[$key]['isSpacer'];
1913 $natVal = $this->isSubMenu($this->menuArr
[$key]['uid']);
1916 $natVal = $this->isActive($this->menuArr
[$key]['uid'], $this->getMPvar($key));
1919 $natVal = $this->isActive($this->menuArr
[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr
[$key]['uid']);
1922 $natVal = $this->isCurrent($this->menuArr
[$key]['uid'], $this->getMPvar($key));
1925 $natVal = $this->isCurrent($this->menuArr
[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr
[$key]['uid']);
1928 $natVal = (bool)$this->menuArr
[$key]['fe_group'];
1936 * Creates an access-key for a TMENU/GMENU menu item based on the menu item titles first letter
1938 * @param string $title Menu item title.
1939 * @return array Returns an array with keys "code" ("accesskey" attribute for the img-tag) and "alt" (text-addition to the "alt" attribute) if an access key was defined. Otherwise array was empty
1942 public function accessKey($title)
1944 $tsfe = $this->getTypoScriptFrontendController();
1945 // The global array ACCESSKEY is used to globally control if letters are already used!!
1947 $title = trim(strip_tags($title));
1948 $titleLen = strlen($title);
1949 for ($a = 0; $a < $titleLen; $a++
) {
1950 $key = strtoupper(substr($title, $a, 1));
1951 if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey
[$key])) {
1952 $tsfe->accessKey
[$key] = 1;
1953 $result['code'] = ' accesskey="' . $key . '"';
1954 $result['alt'] = ' (ALT+' . $key . ')';
1955 $result['key'] = $key;
1963 * Calls a user function for processing of internal data.
1964 * Used for the properties "IProcFunc" and "itemArrayProcFunc"
1966 * @param string $mConfKey Key pointing for the property in the current ->mconf array holding possibly parameters to pass along to the function/method. Currently the keys used are "IProcFunc" and "itemArrayProcFunc".
1967 * @param mixed $passVar A variable to pass to the user function and which should be returned again from the user function. The idea is that the user function modifies this variable according to what you want to achieve and then returns it. For "itemArrayProcFunc" this variable is $this->menuArr, for "IProcFunc" it is $this->I
1968 * @return mixed The processed $passVar
1971 public function userProcess($mConfKey, $passVar)
1973 if ($this->mconf
[$mConfKey]) {
1974 $funcConf = $this->mconf
[$mConfKey . '.'];
1975 $funcConf['parentObj'] = $this;
1976 $passVar = $this->parent_cObj
->callUserFunction($this->mconf
[$mConfKey], $funcConf, $passVar);
1982 * Creates the <A> tag parts for the current item (in $this->I, [A1] and [A2]) based on other information in this array (like $this->I['linkHREF'])
1987 public function setATagParts()
1989 $params = trim($this->I
['val']['ATagParams']) . $this->I
['accessKey']['code'];
1990 $params = $params !== '' ?
' ' . $params : '';
1991 $this->I
['A1'] = '<a ' . GeneralUtility
::implodeAttributes($this->I
['linkHREF'], 1) . $params . '>';
1992 $this->I
['A2'] = '</a>';
1996 * Returns the title for the navigation
1998 * @param string $title The current page title
1999 * @param string $nav_title The current value of the navigation title
2000 * @return string Returns the navigation title if it is NOT blank, otherwise the page title.
2003 public function getPageTitle($title, $nav_title)
2005 return trim($nav_title) !== '' ?
$nav_title : $title;
2009 * Return MPvar string for entry $key in ->menuArr
2011 * @param int $key Pointer to element in ->menuArr
2012 * @return string MP vars for element.
2015 public function getMPvar($key)
2017 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
2018 $localMP_array = $this->MP_array
;
2019 // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
2020 if ($this->menuArr
[$key]['_MP_PARAM']) {
2021 $localMP_array[] = $this->menuArr
[$key]['_MP_PARAM'];
2023 return !empty($localMP_array) ?
implode(',', $localMP_array) : '';
2029 * Returns where clause part to exclude 'not in menu' pages
2031 * @return string where clause part.
2034 public function getDoktypeExcludeWhere()
2036 return $this->doktypeExcludeList ?
' AND pages.doktype NOT IN (' . $this->doktypeExcludeList
. ')' : '';
2040 * Returns an array of banned UIDs (from excludeUidList)
2042 * @return array Array of banned UIDs
2045 public function getBannedUids()
2047 $excludeUidList = isset($this->conf
['excludeUidList.'])
2048 ?
$this->parent_cObj
->stdWrap($this->conf
['excludeUidList'], $this->conf
['excludeUidList.'])
2049 : $this->conf
['excludeUidList'];
2051 if (!trim($excludeUidList)) {
2055 $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page
['uid'], $excludeUidList);
2056 return GeneralUtility
::intExplode(',', $banUidList);
2060 * Calls typolink to create menu item links.
2062 * @param array $page Page record (uid points where to link to)
2063 * @param string $oTarget Target frame/window
2064 * @param bool $no_cache TRUE if caching should be disabled
2065 * @param string $script Alternative script name (unused)
2066 * @param array|string $overrideArray Array to override values in $page, empty string to skip override
2067 * @param string $addParams Parameters to add to URL
2068 * @param int|string $typeOverride "type" value, empty string means "not set"
2069 * @return array See linkData
2071 public function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '')
2074 'parameter' => is_array($overrideArray) && $overrideArray['uid'] ?
$overrideArray['uid'] : $page['uid']
2076 if (MathUtility
::canBeInterpretedAsInteger($typeOverride)) {
2077 $conf['parameter'] .= ',' . (int)$typeOverride;
2080 $conf['additionalParams'] = $addParams;
2083 $conf['no_cache'] = true;
2084 } elseif ($this->useCacheHash
) {
2085 $conf['useCacheHash'] = true;
2088 $conf['target'] = $oTarget;
2090 if ($page['sectionIndex_uid']) {
2091 $conf['section'] = $page['sectionIndex_uid'];
2093 $conf['linkAccessRestrictedPages'] = $this->mconf
['showAccessRestrictedPages'] && $this->mconf
['showAccessRestrictedPages'] !== 'NONE';
2094 $this->parent_cObj
->typoLink('|', $conf);
2095 $LD = $this->parent_cObj
->lastTypoLinkLD
;
2096 $LD['totalURL'] = $this->parent_cObj
->lastTypoLinkUrl
;
2101 * Generates a list of content objects with sectionIndex enabled
2102 * available on a specific page
2104 * Used for menus with sectionIndex enabled
2106 * @param string $altSortField Alternative sorting field
2107 * @param int $pid The page id to search for sections
2108 * @throws \UnexpectedValueException if the query to fetch the content elements unexpectedly fails
2111 protected function sectionIndex($altSortField, $pid = null)
2113 $pid = (int)($pid ?
: $this->id
);
2114 $basePageRow = $this->sys_page
->getPage($pid);
2115 if (!is_array($basePageRow)) {
2118 $tsfe = $this->getTypoScriptFrontendController();
2119 $configuration = $this->mconf
['sectionIndex.'];
2121 if (trim($configuration['useColPos']) !== '' ||
is_array($configuration['useColPos.'])) {
2122 $useColPos = $tsfe->cObj
->stdWrap($configuration['useColPos'], $configuration['useColPos.']);
2123 $useColPos = (int)$useColPos;
2125 $selectSetup = array(
2126 'pidInList' => $pid,
2127 'orderBy' => $altSortField,
2128 'languageField' => 'sys_language_uid',
2129 'where' => $useColPos >= 0 ?
'colPos=' . $useColPos : ''
2131 if ($basePageRow['content_from_pid']) {
2132 // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
2133 // the referenced page
2134 $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
2136 $resource = $this->parent_cObj
->exec_getQuery('tt_content', $selectSetup);
2138 $message = 'SectionIndex: Query to fetch the content elements failed!';
2139 throw new \
UnexpectedValueException($message, 1337334849);
2142 while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($resource)) {
2143 $this->sys_page
->versionOL('tt_content', $row);
2144 if ($tsfe->sys_language_contentOL
&& $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
2145 $row = $this->sys_page
->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL
);
2147 if ($this->mconf
['sectionIndex.']['type'] !== 'all') {
2148 $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
2149 $doHeaderCheck = $this->mconf
['sectionIndex.']['type'] === 'header';
2150 $isValidHeader = ((int)$row['header_layout'] !== 100 ||
!empty($this->mconf
['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
2151 if (!$doIncludeInSectionIndex ||
$doHeaderCheck && !$isValidHeader) {
2155 if (is_array($row)) {
2157 $result[$uid] = $basePageRow;
2158 $result[$uid]['title'] = $row['header'];
2159 $result[$uid]['nav_title'] = $row['header'];
2160 // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
2161 $result[$uid]['nav_hide'] = 0;
2162 $result[$uid]['subtitle'] = $row['subheader'];
2163 $result[$uid]['starttime'] = $row['starttime'];
2164 $result[$uid]['endtime'] = $row['endtime'];
2165 $result[$uid]['fe_group'] = $row['fe_group'];
2166 $result[$uid]['media'] = $row['media'];
2167 $result[$uid]['header_layout'] = $row['header_layout'];
2168 $result[$uid]['bodytext'] = $row['bodytext'];
2169 $result[$uid]['image'] = $row['image'];
2170 $result[$uid]['sectionIndex_uid'] = $uid;
2173 $this->getDatabaseConnection()->sql_free_result($resource);
2178 * Returns the sys_page object
2180 * @return \TYPO3\CMS\Frontend\Page\PageRepository
2182 public function getSysPage()
2184 return $this->sys_page
;
2188 * Returns the parent content object
2190 * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
2192 public function getParentContentObject()
2194 return $this->parent_cObj
;
2198 * @return DatabaseConnection
2200 protected function getDatabaseConnection()
2202 return $GLOBALS['TYPO3_DB'];
2206 * @return TypoScriptFrontendController
2208 protected function getTypoScriptFrontendController()
2210 return $GLOBALS['TSFE'];
2214 * @return TimeTracker
2216 protected function getTimeTracker()
2218 return $GLOBALS['TT'];
2222 * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
2224 protected function getCache()
2226 return GeneralUtility
::makeInstance(CacheManager
::class)->getCache('cache_hash');