02587a66af11701b0bb097571c7713e1feb21c7f
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / Menu / AbstractMenuContentObject.php
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
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\MathUtility;
19 use TYPO3\CMS\Frontend\Page\PageRepository;
20
21 /**
22 * Generating navigation / menus from TypoScript
23 *
24 * Base class. The HMENU content object uses this (or more precisely one of the extension classes).
25 * Among others the class generates an array of menuitems. Thereafter functions from the subclasses are called.
26 * The class is ALWAYS used through extension classes (like GraphicalMenuContentObject or TextMenuContentObject which are classics) and
27 *
28 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
29 */
30 class AbstractMenuContentObject {
31
32 /**
33 * tells you which menu-number this is. This is important when getting data from the setup
34 *
35 * @var int
36 */
37 public $menuNumber = 1;
38
39 /**
40 * 0 = rootFolder
41 *
42 * @var int
43 */
44 public $entryLevel = 0;
45
46 /**
47 * The doktype-number that defines a spacer
48 *
49 * @var string
50 */
51 public $spacerIDList = '199';
52
53 /**
54 * Doktypes that define which should not be included in a menu
55 *
56 * @var string
57 */
58 public $doktypeExcludeList = '6';
59
60 /**
61 * @var array
62 */
63 public $alwaysActivePIDlist = array();
64
65 /**
66 * @var string
67 */
68 public $imgNamePrefix = 'img';
69
70 /**
71 * @var int
72 */
73 public $imgNameNotRandom = 0;
74
75 /**
76 * @var bool
77 */
78 public $debug = 0;
79
80 /**
81 * Loaded with the parent cObj-object when a new HMENU is made
82 *
83 * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
84 */
85 public $parent_cObj;
86
87 /**
88 * @var string
89 */
90 public $GMENU_fixKey = 'gmenu';
91
92 /**
93 * accumulation of mount point data
94 *
95 * @var array
96 */
97 public $MP_array = array();
98
99 /**
100 * HMENU configuration
101 *
102 * @var array
103 */
104 public $conf = array();
105
106 /**
107 * xMENU configuration (TMENU, GMENU etc)
108 *
109 * @var array
110 */
111 public $mconf = array();
112
113 /**
114 * template-object
115 *
116 * @var \TYPO3\CMS\Core\TypoScript\TemplateService
117 */
118 public $tmpl;
119
120 /**
121 * sys_page-object, pagefunctions
122 *
123 * @var \TYPO3\CMS\Frontend\Page\PageRepository
124 */
125 public $sys_page;
126
127 /**
128 * The base page-id of the menu.
129 *
130 * @var int
131 */
132 public $id;
133
134 /**
135 * Holds the page uid of the NEXT page in the root line from the page pointed to by entryLevel;
136 * Used to expand the menu automatically if in a certain root line.
137 *
138 * @var string
139 */
140 public $nextActive;
141
142 /**
143 * The array of menuItems which is built
144 *
145 * @var array
146 */
147 public $menuArr;
148
149 /**
150 * @var string
151 */
152 public $hash;
153
154 /**
155 * @var array
156 */
157 public $result = array();
158
159 /**
160 * Is filled with an array of page uid numbers + RL parameters which are in the current
161 * root line (used to evaluate whether a menu item is in active state)
162 *
163 * @var string
164 */
165 public $rL_uidRegister = '';
166
167 /**
168 * @var string
169 */
170 public $INPfixMD5;
171
172 /**
173 * @var array
174 */
175 public $I;
176
177 /**
178 * @var string
179 */
180 public $WMresult;
181
182 /**
183 * @var string
184 */
185 public $WMfreezePrefix;
186
187 /**
188 * @var int
189 */
190 public $WMmenuItems;
191
192 /**
193 * @var array
194 */
195 public $WMsubmenuObjSuffixes;
196
197 /**
198 * @var string
199 */
200 public $WMextraScript;
201
202 /**
203 * Can be set to contain menu item arrays for sub-levels.
204 *
205 * @var string
206 */
207 public $alternativeMenuTempArray = '';
208
209 /**
210 * Will be 'id' in XHTML-mode
211 *
212 * @var string
213 */
214 public $nameAttribute = 'name';
215
216 /**
217 * TRUE to use cHash in generated link (normally only for the language
218 * selector and if parameters exist in the URL).
219 *
220 * @var bool
221 */
222 protected $useCacheHash = FALSE;
223
224 /**
225 * The initialization of the object. This just sets some internal variables.
226 *
227 * @param object $tmpl The $GLOBALS['TSFE']->tmpl object
228 * @param object $sys_page The $GLOBALS['TSFE']->sys_page object
229 * @param int $id A starting point page id. This should probably be blank since the 'entryLevel' value will be used then.
230 * @param array $conf The TypoScript configuration for the HMENU cObject
231 * @param int $menuNumber Menu number; 1,2,3. Should probably be '1'
232 * @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")
233 * @return bool Returns TRUE on success
234 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::HMENU()
235 */
236 public function start(&$tmpl, &$sys_page, $id, $conf, $menuNumber, $objSuffix = '') {
237 // Init:
238 $this->conf = $conf;
239 $this->menuNumber = $menuNumber;
240 $this->mconf = $conf[$this->menuNumber . $objSuffix . '.'];
241 $this->debug = $GLOBALS['TSFE']->debug;
242 // In XHTML there is no "name" attribute anymore
243 switch ($GLOBALS['TSFE']->xhtmlDoctype) {
244 case 'xhtml_strict':
245
246 case 'xhtml_11':
247
248 case 'xhtml_2':
249
250 case 'html5':
251 $this->nameAttribute = 'id';
252 break;
253 default:
254 $this->nameAttribute = 'name';
255 }
256 // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the sys_page object
257 if ($this->conf[$this->menuNumber . $objSuffix] && is_object($tmpl) && is_object($sys_page)) {
258 $this->tmpl = $tmpl;
259 $this->sys_page = $sys_page;
260 // alwaysActivePIDlist initialized:
261 if (trim($this->conf['alwaysActivePIDlist']) || isset($this->conf['alwaysActivePIDlist.'])) {
262 if (isset($this->conf['alwaysActivePIDlist.'])) {
263 $this->conf['alwaysActivePIDlist'] = $this->parent_cObj->stdWrap(
264 $this->conf['alwaysActivePIDlist'],
265 $this->conf['alwaysActivePIDlist.']
266 );
267 }
268 $this->alwaysActivePIDlist = GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
269 }
270 // 'not in menu' doktypes
271 if ($this->conf['excludeDoktypes']) {
272 $this->doktypeExcludeList = $GLOBALS['TYPO3_DB']->cleanIntList($this->conf['excludeDoktypes']);
273 }
274 // EntryLevel
275 $this->entryLevel = $this->parent_cObj->getKey(
276 isset($conf['entryLevel.']) ? $this->parent_cObj->stdWrap(
277 $conf['entryLevel'],
278 $conf['entryLevel.']
279 ) : $conf['entryLevel'],
280 $this->tmpl->rootLine
281 );
282 // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
283 // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
284 if ($id) {
285 $this->id = (int)$id;
286 } else {
287 // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
288 $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
289 // Traverse rootline to build MP_array of pages BEFORE the entryLevel
290 // (MP var for ->id is picked up in the next part of the code...)
291 foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
292 // For overlaid mount points, set the variable right now:
293 if ($levelRec['_MP_PARAM'] && $levelRec['_MOUNT_OL']) {
294 $this->MP_array[] = $levelRec['_MP_PARAM'];
295 }
296 // Break when entry level is reached:
297 if ($entryLevel >= $this->entryLevel) {
298 break;
299 }
300 // For normal mount points, set the variable for next level.
301 if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
302 $this->MP_array[] = $levelRec['_MP_PARAM'];
303 }
304 }
305 }
306 // Return FALSE if no page ID was set (thus no menu of subpages can be made).
307 if ($this->id <= 0) {
308 return FALSE;
309 }
310 // Check if page is a mount point, and if so set id and MP_array
311 // (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...)
312 $mount_info = $this->sys_page->getMountPointInfo($this->id);
313 if (is_array($mount_info)) {
314 $this->MP_array[] = $mount_info['MPvar'];
315 $this->id = $mount_info['mount_pid'];
316 }
317 // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
318 // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
319 if (!is_array($this->rL_uidRegister)) {
320 $rl_MParray = array();
321 foreach ($this->tmpl->rootLine as $v_rl) {
322 // For overlaid mount points, set the variable right now:
323 if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
324 $rl_MParray[] = $v_rl['_MP_PARAM'];
325 }
326 // Add to register:
327 $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
328 (count($rl_MParray)
329 ? ':' . implode(',', $rl_MParray)
330 : ''
331 );
332 // For normal mount points, set the variable for next level.
333 if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
334 $rl_MParray[] = $v_rl['_MP_PARAM'];
335 }
336 }
337 }
338 // Set $directoryLevel so the following evalution of the nextActive will not return
339 // an invalid value if .special=directory was set
340 $directoryLevel = 0;
341 if ($this->conf['special'] == 'directory') {
342 $value = isset($this->conf['special.']['value.']) ? $this->parent_cObj->stdWrap(
343 $this->conf['special.']['value'],
344 $this->conf['special.']['value.']
345 ) : $this->conf['special.']['value'];
346 if ($value == '') {
347 $value = $GLOBALS['TSFE']->page['uid'];
348 }
349 $directoryLevel = (int)$GLOBALS['TSFE']->tmpl->getRootlineLevel($value);
350 }
351 // 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
352 // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
353 $startLevel = $directoryLevel ?: $this->entryLevel;
354 $currentLevel = $startLevel + $this->menuNumber;
355 if (is_array($this->tmpl->rootLine[$currentLevel])) {
356 $nextMParray = $this->MP_array;
357 if (!count($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
358 // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
359 // otherwise automatic expansion will not work
360 $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
361 if (isset($parentRecord['_MP_PARAM'])) {
362 $nextMParray[] = $parentRecord['_MP_PARAM'];
363 }
364 }
365 // In overlay mode, add next level MPvars as well:
366 if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
367 $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
368 }
369 $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
370 (count($nextMParray)
371 ? ':' . implode(',', $nextMParray)
372 : ''
373 );
374 } else {
375 $this->nextActive = '';
376 }
377 // imgNamePrefix
378 if ($this->mconf['imgNamePrefix']) {
379 $this->imgNamePrefix = $this->mconf['imgNamePrefix'];
380 }
381 $this->imgNameNotRandom = $this->mconf['imgNameNotRandom'];
382 $retVal = TRUE;
383 } else {
384 $GLOBALS['TT']->setTSlogMessage('ERROR in menu', 3);
385 $retVal = FALSE;
386 }
387 return $retVal;
388 }
389
390 /**
391 * Creates the menu in the internal variables, ready for output.
392 * Basically this will read the page records needed and fill in the internal $this->menuArr
393 * Based on a hash of this array and some other variables the $this->result variable will be loaded either from cache OR by calling the generate() method of the class to create the menu for real.
394 *
395 * @return void
396 */
397 public function makeMenu() {
398 if ($this->id) {
399 $this->useCacheHash = FALSE;
400
401 // Initializing showAccessRestrictedPages
402 if ($this->mconf['showAccessRestrictedPages']) {
403 // SAVING where_groupAccess
404 $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
405 // Temporarily removing fe_group checking!
406 $this->sys_page->where_groupAccess = '';
407 }
408
409 $menuItems = $this->prepareMenuItems();
410
411 $c = 0;
412 $c_b = 0;
413 $minItems = (int)($this->mconf['minItems'] ?: $this->conf['minItems']);
414 $maxItems = (int)($this->mconf['maxItems'] ?: $this->conf['maxItems']);
415 $begin = $this->parent_cObj->calc($this->mconf['begin'] ? $this->mconf['begin'] : $this->conf['begin']);
416 $minItemsConf = isset($this->mconf['minItems.']) ? $this->mconf['minItems.'] : (isset($this->conf['minItems.']) ? $this->conf['minItems.'] : NULL);
417 $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
418 $maxItemsConf = isset($this->mconf['maxItems.']) ? $this->mconf['maxItems.'] : (isset($this->conf['maxItems.']) ? $this->conf['maxItems.'] : NULL);
419 $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
420 $beginConf = isset($this->mconf['begin.']) ? $this->mconf['begin.'] : (isset($this->conf['begin.']) ? $this->conf['begin.'] : NULL);
421 $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
422 $banUidArray = $this->getBannedUids();
423 // Fill in the menuArr with elements that should go into the menu:
424 $this->menuArr = array();
425 foreach ($menuItems as $data) {
426 $spacer = GeneralUtility::inList(
427 $this->spacerIDList,
428 $data['doktype']
429 ) || $data['ITEM_STATE'] === 'SPC';
430 // if item is a spacer, $spacer is set
431 if ($this->filterMenuPages($data, $banUidArray, $spacer)) {
432 $c_b++;
433 // If the beginning item has been reached.
434 if ($begin <= $c_b) {
435 $this->menuArr[$c] = $data;
436 $this->menuArr[$c]['isSpacer'] = $spacer;
437 $c++;
438 if ($maxItems && $c >= $maxItems) {
439 break;
440 }
441 }
442 }
443 }
444 // Fill in fake items, if min-items is set.
445 if ($minItems) {
446 while ($c < $minItems) {
447 $this->menuArr[$c] = array(
448 'title' => '...',
449 'uid' => $GLOBALS['TSFE']->id
450 );
451 $c++;
452 }
453 }
454 // Passing the menuArr through a user defined function:
455 if ($this->mconf['itemArrayProcFunc']) {
456 if (!is_array($this->parentMenuArr)) {
457 $this->parentMenuArr = array();
458 }
459 $this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
460 }
461 // Setting number of menu items
462 $GLOBALS['TSFE']->register['count_menuItems'] = count($this->menuArr);
463 $this->hash = md5(
464 serialize($this->menuArr) .
465 serialize($this->mconf) .
466 serialize($this->tmpl->rootLine) .
467 serialize($this->MP_array)
468 );
469 // Get the cache timeout:
470 if ($this->conf['cache_period']) {
471 $cacheTimeout = $this->conf['cache_period'];
472 } else {
473 $cacheTimeout = $GLOBALS['TSFE']->get_cache_timeout();
474 }
475 $cachedData = $this->sys_page->getHash($this->hash);
476 if (!is_array($cachedData)) {
477 $this->generate();
478 $this->sys_page->storeHash($this->hash, $this->result, 'MENUDATA', $cacheTimeout);
479 } else {
480 $this->result = $cachedData;
481 }
482 // End showAccessRestrictedPages
483 if ($this->mconf['showAccessRestrictedPages']) {
484 // RESTORING where_groupAccess
485 $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
486 }
487 }
488 }
489
490 /**
491 * Gets an array of page rows and removes all, which are not accessible
492 *
493 * @param array $pages
494 * @return array
495 */
496 protected function removeInaccessiblePages(array $pages) {
497 $banned = $this->getBannedUids();
498 $filteredPages = array();
499 foreach ($pages as $aPage) {
500 if ($this->filterMenuPages($aPage, $banned, $aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
501 $filteredPages[$aPage['uid']] = $aPage;
502 }
503 }
504 return $filteredPages;
505 }
506
507 /**
508 * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
509 *
510 * @return array
511 */
512 protected function prepareMenuItems() {
513 $menuItems = array();
514 $alternativeSortingField = trim($this->mconf['alternativeSortingField']) ?: 'sorting';
515
516 // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
517 $additionalWhere = isset($this->mconf['additionalWhere']) ? $this->mconf['additionalWhere'] : '';
518 if (isset($this->mconf['additionalWhere.'])) {
519 $additionalWhere = $this->parent_cObj->stdWrap($additionalWhere, $this->mconf['additionalWhere.']);
520
521 }
522
523 // ... only for the FIRST level of a HMENU
524 if ($this->menuNumber == 1 && $this->conf['special']) {
525 $value = isset($this->conf['special.']['value.'])
526 ? $this->parent_cObj->stdWrap($this->conf['special.']['value'],$this->conf['special.']['value.'])
527 : $this->conf['special.']['value'];
528 switch ($this->conf['special']) {
529 case 'userfunction':
530 $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
531 break;
532 case 'language':
533 $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
534 break;
535 case 'directory':
536 $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
537 break;
538 case 'list':
539 $menuItems = $this->prepareMenuItemsForListMenu($value);
540 break;
541 case 'updated':
542 $menuItems = $this->prepareMenuItemsForUpdatedMenu(
543 $value,
544 $this->mconf['alternativeSortingField'] ?: FALSE
545 );
546 break;
547 case 'keywords':
548 $menuItems = $this->prepareMenuItemsForKeywordsMenu(
549 $value,
550 $this->mconf['alternativeSortingField'] ?: FALSE
551 );
552 break;
553 case 'categories':
554 /** @var \TYPO3\CMS\Frontend\ContentObject\Menu\CategoryMenuUtility $categoryMenuUtility */
555 $categoryMenuUtility = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\Menu\CategoryMenuUtility::class);
556 $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
557 break;
558 case 'rootline':
559 $menuItems = $this->prepareMenuItemsForRootlineMenu();
560 break;
561 case 'browse':
562 $menuItems = $this->prepareMenuitemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
563 break;
564 }
565 if ($this->mconf['sectionIndex']) {
566 $sectionIndexes = array();
567 foreach ($menuItems as $page) {
568 $sectionIndexes = $sectionIndexes + $this->sectionIndex($alternativeSortingField, $page['uid']);
569 }
570 $menuItems = $sectionIndexes;
571 }
572 } elseif (is_array($this->alternativeMenuTempArray)) {
573 // Setting $menuItems array if not level 1.
574 $menuItems = $this->alternativeMenuTempArray;
575 } elseif ($this->mconf['sectionIndex']) {
576 $menuItems = $this->sectionIndex($alternativeSortingField);
577 } else {
578 // Default: Gets a hierarchical menu based on subpages of $this->id
579 $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
580 }
581 return $menuItems;
582 }
583
584 /**
585 * Fetches all menuitems if special = userfunction is set
586 *
587 * @param string $specialValue The value from special.value
588 * @param string $sortingField The sorting field
589 * @return array
590 */
591 protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField) {
592 $menuItems = $this->parent_cObj->callUserFunction(
593 $this->conf['special.']['userFunc'],
594 array_merge($this->conf['special.'], array('value' => $specialValue, '_altSortField' => $sortingField)),
595 ''
596 );
597 if (!is_array($menuItems)) {
598 $menuItems = array();
599 }
600 return $menuItems;
601 }
602
603 /**
604 * Fetches all menuitems if special = language is set
605 *
606 * @param string $specialValue The value from special.value
607 * @return array
608 */
609 protected function prepareMenuItemsForLanguageMenu($specialValue) {
610 $menuItems = array();
611 // Getting current page record NOT overlaid by any translation:
612 $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $GLOBALS['TSFE']->page['uid']);
613 // Traverse languages set up:
614 $languageItems = GeneralUtility::intExplode(',', $specialValue);
615 foreach ($languageItems as $sUid) {
616 // Find overlay record:
617 if ($sUid) {
618 $lRecs = $this->sys_page->getPageOverlay($GLOBALS['TSFE']->page['uid'], $sUid);
619 } else {
620 $lRecs = array();
621 }
622 // Checking if the "disabled" state should be set.
623 if (GeneralUtility::hideIfNotTranslated($GLOBALS['TSFE']->page['l18n_cfg']) && $sUid &&
624 !count($lRecs) || $GLOBALS['TSFE']->page['l18n_cfg'] & 1 &&
625 (!$sUid || !count($lRecs)) ||
626 !$this->conf['special.']['normalWhenNoLanguage'] && $sUid && !count($lRecs)
627 ) {
628 $iState = $GLOBALS['TSFE']->sys_language_uid == $sUid ? 'USERDEF2' : 'USERDEF1';
629 } else {
630 $iState = $GLOBALS['TSFE']->sys_language_uid == $sUid ? 'ACT' : 'NO';
631 }
632 if ($this->conf['addQueryString']) {
633 $getVars = $this->parent_cObj->getQueryArguments(
634 $this->conf['addQueryString.'],
635 array('L' => $sUid),
636 TRUE
637 );
638 $this->analyzeCacheHashRequirements($getVars);
639 } else {
640 $getVars = '&L=' . $sUid;
641 }
642 // Adding menu item:
643 $menuItems[] = array_merge(
644 array_merge($currentPageWithNoOverlay, $lRecs),
645 array(
646 'ITEM_STATE' => $iState,
647 '_ADD_GETVARS' => $getVars,
648 '_SAFE' => TRUE
649 )
650 );
651 }
652 return $menuItems;
653 }
654
655 /**
656 * Fetches all menuitems if special = directory is set
657 *
658 * @param string $specialValue The value from special.value
659 * @param string $sortingField The sorting field
660 * @return array
661 */
662 protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField) {
663 $menuItems = array();
664 if ($specialValue == '') {
665 $specialValue = $GLOBALS['TSFE']->page['uid'];
666 }
667 $items = GeneralUtility::intExplode(',', $specialValue);
668 foreach ($items as $id) {
669 $MP = $this->tmpl->getFromMPmap($id);
670 // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
671 $mount_info = $this->sys_page->getMountPointInfo($id);
672 if (is_array($mount_info)) {
673 if ($mount_info['overlay']) {
674 // Overlays should already have their full MPvars calculated:
675 $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
676 $MP = $MP ? $MP : $mount_info['MPvar'];
677 } else {
678 $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
679 }
680 $id = $mount_info['mount_pid'];
681 }
682 // Get sub-pages:
683 $res = $this->parent_cObj->exec_getQuery('pages', array('pidInList' => $id, 'orderBy' => $sortingField));
684 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
685 $GLOBALS['TSFE']->sys_page->versionOL('pages', $row, TRUE);
686 if (is_array($row)) {
687 // Keep mount point?
688 $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
689 // There is a valid mount point.
690 if (is_array($mount_info) && $mount_info['overlay']) {
691 // Using "getPage" is OK since we need the check for enableFields
692 // AND for type 2 of mount pids we DO require a doktype < 200!
693 $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
694 if (count($mp_row)) {
695 $row = $mp_row;
696 $row['_MP_PARAM'] = $mount_info['MPvar'];
697 } else {
698 // If the mount point could not be fetched with respect
699 // to enableFields, unset the row so it does not become a part of the menu!
700 unset($row);
701 }
702 }
703 // Add external MP params, then the row:
704 if (is_array($row)) {
705 if ($MP) {
706 $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
707 }
708 $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
709 }
710 }
711 }
712 }
713 return $menuItems;
714 }
715
716 /**
717 * Fetches all menuitems if special = list is set
718 *
719 * @param string $specialValue The value from special.value
720 * @return array
721 */
722 protected function prepareMenuItemsForListMenu($specialValue) {
723 $menuItems = array();
724 if ($specialValue == '') {
725 $specialValue = $this->id;
726 }
727 /** @var \TYPO3\CMS\Core\Database\RelationHandler $loadDB*/
728 $loadDB = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
729 $loadDB->setFetchAllFields(TRUE);
730 $loadDB->start($specialValue, 'pages');
731 $loadDB->additionalWhere['pages'] = $this->parent_cObj->enableFields('pages');
732 $loadDB->getFromDB();
733 foreach ($loadDB->itemArray as $val) {
734 $MP = $this->tmpl->getFromMPmap($val['id']);
735 // Keep mount point?
736 $mount_info = $this->sys_page->getMountPointInfo($val['id']);
737 // There is a valid mount point.
738 if (is_array($mount_info) && $mount_info['overlay']) {
739 // Using "getPage" is OK since we need the check for enableFields
740 // AND for type 2 of mount pids we DO require a doktype < 200!
741 $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
742 if (count($mp_row)) {
743 $row = $mp_row;
744 $row['_MP_PARAM'] = $mount_info['MPvar'];
745 // Overlays should already have their full MPvars calculated
746 if ($mount_info['overlay']) {
747 $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
748 if ($MP) {
749 unset($row['_MP_PARAM']);
750 }
751 }
752 } else {
753 // If the mount point could not be fetched with respect to
754 // enableFields, unset the row so it does not become a part of the menu!
755 unset($row);
756 }
757 } else {
758 $row = $loadDB->results['pages'][$val['id']];
759 }
760 // Add versioning overlay for current page (to respect workspaces)
761 if (is_array($row)) {
762 $this->sys_page->versionOL('pages', $row, TRUE);
763 }
764 // Add external MP params, then the row:
765 if (is_array($row)) {
766 if ($MP) {
767 $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
768 }
769 $menuItems[] = $this->sys_page->getPageOverlay($row);
770 }
771 }
772 return $menuItems;
773 }
774
775 /**
776 * Fetches all menuitems if special = updated is set
777 *
778 * @param string $specialValue The value from special.value
779 * @param string $sortingField The sorting field
780 * @return array
781 */
782 protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField) {
783 $menuItems = array();
784 if ($specialValue == '') {
785 $specialValue = $GLOBALS['TSFE']->page['uid'];
786 }
787 $items = GeneralUtility::intExplode(',', $specialValue);
788 if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
789 $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
790 } else {
791 $depth = 20;
792 }
793 // Max number of items
794 $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
795 $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
796 if (!$limit) {
797 $limit = 10;
798 }
799 // *'auto', 'manual', 'tstamp'
800 $mode = $this->conf['special.']['mode'];
801 // Get id's
802 $id_list_arr = array();
803 foreach ($items as $id) {
804 $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
805 $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $bA, $bA - 1);
806 }
807 $id_list = implode(',', $id_list_arr);
808 // Get sortField (mode)
809 switch ($mode) {
810 case 'starttime':
811 $sortField = 'starttime';
812 break;
813 case 'lastUpdated':
814
815 case 'manual':
816 $sortField = 'lastUpdated';
817 break;
818 case 'tstamp':
819 $sortField = 'tstamp';
820 break;
821 case 'crdate':
822 $sortField = 'crdate';
823 break;
824 default:
825 $sortField = 'SYS_LASTCHANGED';
826 }
827 $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
828 if ($this->conf['special.']['excludeNoSearchPages']) {
829 $extraWhere .= ' AND pages.no_search=0';
830 }
831 if ($maxAge > 0) {
832 $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
833 }
834 $res = $this->parent_cObj->exec_getQuery('pages', array(
835 'pidInList' => '0',
836 'uidInList' => $id_list,
837 'where' => $sortField . '>=0' . $extraWhere,
838 'orderBy' => $sortingField ?: $sortField . ' DESC',
839 'max' => $limit
840 ));
841 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
842 $GLOBALS['TSFE']->sys_page->versionOL('pages', $row, TRUE);
843 if (is_array($row)) {
844 $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
845 }
846 }
847 return $menuItems;
848 }
849
850 /**
851 * Fetches all menuitems if special = keywords is set
852 *
853 * @param string $specialValue The value from special.value
854 * @param string $sortingField The sorting field
855 * @return array
856 */
857 protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField) {
858 $menuItems = array();
859 list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
860 if (!$specialValue) {
861 $specialValue = $GLOBALS['TSFE']->page['uid'];
862 }
863 if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
864 $kw = isset($this->conf['special.']['setKeywords.']) ? $this->parent_cObj->stdWrap($this->conf['special.']['setKeywords'], $this->conf['special.']['setKeywords.']) : $this->conf['special.']['setKeywords'];
865 } else {
866 // The page record of the 'value'.
867 $value_rec = $this->sys_page->getPage($specialValue);
868 $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
869 // keywords.
870 $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
871 }
872 // *'auto', 'manual', 'tstamp'
873 $mode = $this->conf['special.']['mode'];
874 switch ($mode) {
875 case 'starttime':
876 $sortField = 'starttime';
877 break;
878 case 'lastUpdated':
879
880 case 'manual':
881 $sortField = 'lastUpdated';
882 break;
883 case 'tstamp':
884 $sortField = 'tstamp';
885 break;
886 case 'crdate':
887 $sortField = 'crdate';
888 break;
889 default:
890 $sortField = 'SYS_LASTCHANGED';
891 }
892 // Depth, limit, extra where
893 if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
894 $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
895 } else {
896 $depth = 20;
897 }
898 // Max number of items
899 $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
900 $extraWhere = ' AND pages.uid<>' . $specialValue . ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
901 if ($this->conf['special.']['excludeNoSearchPages']) {
902 $extraWhere .= ' AND pages.no_search=0';
903 }
904 // Start point
905 $eLevel = $this->parent_cObj->getKey(isset($this->conf['special.']['entryLevel.'])
906 ? $this->parent_cObj->stdWrap($this->conf['special.']['entryLevel'], $this->conf['special.']['entryLevel.'])
907 : $this->conf['special.']['entryLevel'], $this->tmpl->rootLine
908 );
909 $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
910 // Which field is for keywords
911 $kfield = 'keywords';
912 if ($this->conf['special.']['keywordsField']) {
913 list($kfield) = explode(' ', trim($this->conf['special.']['keywordsField']));
914 }
915 // If there are keywords and the startuid is present
916 if ($kw && $startUid) {
917 $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
918 $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
919 $kwArr = explode(',', $kw);
920 foreach ($kwArr as $word) {
921 $word = trim($word);
922 if ($word) {
923 $keyWordsWhereArr[] = $kfield . ' LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($word, 'pages') . '%\'';
924 }
925 }
926 $res = $this->parent_cObj->exec_getQuery('pages', array(
927 'pidInList' => '0',
928 'uidInList' => $id_list,
929 'where' => '(' . implode(' OR ', $keyWordsWhereArr) . ')' . $extraWhere,
930 'orderBy' => $sortingField ?: $sortField . ' desc',
931 'max' => $limit
932 ));
933 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
934 $GLOBALS['TSFE']->sys_page->versionOL('pages', $row, TRUE);
935 if (is_array($row)) {
936 $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
937 }
938 }
939 }
940 return $menuItems;
941 }
942
943 /**
944 * Fetches all menuitems if special = rootline is set
945 *
946 * @return array
947 */
948 protected function prepareMenuItemsForRootlineMenu() {
949 $menuItems = array();
950 $range = isset($this->conf['special.']['range.'])
951 ? $this->parent_cObj->stdWrap($this->conf['special.']['range'], $this->conf['special.']['range.'])
952 : $this->conf['special.']['range'];
953 $begin_end = explode('|', $range);
954 $begin_end[0] = (int)$begin_end[0];
955 if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
956 $begin_end[1] = -1;
957 }
958 $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
959 $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
960 if ($endKey < $beginKey) {
961 $endKey = $beginKey;
962 }
963 $rl_MParray = array();
964 foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
965 // For overlaid mount points, set the variable right now:
966 if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
967 $rl_MParray[] = $v_rl['_MP_PARAM'];
968 }
969 // Traverse rootline:
970 if ($k_rl >= $beginKey && $k_rl <= $endKey) {
971 $temp_key = $k_rl;
972 $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
973 if (count($menuItems[$temp_key])) {
974 // If there are no specific target for the page, put the level specific target on.
975 if (!$menuItems[$temp_key]['target']) {
976 $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
977 $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
978 }
979 } else {
980 unset($menuItems[$temp_key]);
981 }
982 }
983 // For normal mount points, set the variable for next level.
984 if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
985 $rl_MParray[] = $v_rl['_MP_PARAM'];
986 }
987 }
988 // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
989 if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
990 $menuItems = array_reverse($menuItems);
991 $rl_MParray = array_reverse($rl_MParray);
992 }
993 return $menuItems;
994 }
995
996 /**
997 * Fetches all menuitems if special = browse is set
998 *
999 * @param string $specialValue The value from special.value
1000 * @param string $sortingField The sorting field
1001 * @param string $additionalWhere Additional WHERE clause
1002 * @return array
1003 */
1004 protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere) {
1005 $menuItems = array();
1006 list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
1007 if (!$specialValue) {
1008 $specialValue = $GLOBALS['TSFE']->page['uid'];
1009 }
1010 // Will not work out of rootline
1011 if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1012 $recArr = array();
1013 // The page record of the 'value'.
1014 $value_rec = $this->sys_page->getPage($specialValue);
1015 // 'up' page cannot be outside rootline
1016 if ($value_rec['pid']) {
1017 // The page record of 'up'.
1018 $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1019 }
1020 // If the 'up' item was NOT level 0 in rootline...
1021 if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1022 // The page record of "index".
1023 $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1024 }
1025 // check if certain pages should be excluded
1026 $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1027 if ($this->conf['special.']['excludeNoSearchPages']) {
1028 $additionalWhere .= ' AND pages.no_search=0';
1029 }
1030 // prev / next is found
1031 $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1032 $lastKey = 0;
1033 $nextActive = 0;
1034 foreach ($prevnext_menu as $k_b => $v_b) {
1035 if ($nextActive) {
1036 $recArr['next'] = $v_b;
1037 $nextActive = 0;
1038 }
1039 if ($v_b['uid'] == $specialValue) {
1040 if ($lastKey) {
1041 $recArr['prev'] = $prevnext_menu[$lastKey];
1042 }
1043 $nextActive = 1;
1044 }
1045 $lastKey = $k_b;
1046 }
1047 reset($prevnext_menu);
1048 $recArr['first'] = pos($prevnext_menu);
1049 end($prevnext_menu);
1050 $recArr['last'] = pos($prevnext_menu);
1051 // prevsection / nextsection is found
1052 // You can only do this, if there is a valid page two levels up!
1053 if (is_array($recArr['index'])) {
1054 $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1055 $lastKey = 0;
1056 $nextActive = 0;
1057 foreach ($prevnextsection_menu as $k_b => $v_b) {
1058 if ($nextActive) {
1059 $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1060 if (count($sectionRec_temp)) {
1061 reset($sectionRec_temp);
1062 $recArr['nextsection'] = pos($sectionRec_temp);
1063 end($sectionRec_temp);
1064 $recArr['nextsection_last'] = pos($sectionRec_temp);
1065 $nextActive = 0;
1066 }
1067 }
1068 if ($v_b['uid'] == $value_rec['pid']) {
1069 if ($lastKey) {
1070 $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1071 if (count($sectionRec_temp)) {
1072 reset($sectionRec_temp);
1073 $recArr['prevsection'] = pos($sectionRec_temp);
1074 end($sectionRec_temp);
1075 $recArr['prevsection_last'] = pos($sectionRec_temp);
1076 }
1077 }
1078 $nextActive = 1;
1079 }
1080 $lastKey = $k_b;
1081 }
1082 }
1083 if ($this->conf['special.']['items.']['prevnextToSection']) {
1084 if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1085 $recArr['prev'] = $recArr['prevsection_last'];
1086 }
1087 if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1088 $recArr['next'] = $recArr['nextsection'];
1089 }
1090 }
1091 $items = explode('|', $this->conf['special.']['items']);
1092 $c = 0;
1093 foreach ($items as $k_b => $v_b) {
1094 $v_b = strtolower(trim($v_b));
1095 if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1096 $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1097 }
1098 if (is_array($recArr[$v_b])) {
1099 $menuItems[$c] = $recArr[$v_b];
1100 if ($this->conf['special.'][$v_b . '.']['target']) {
1101 $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1102 }
1103 $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1104 if (is_array($tmpSpecialFields)) {
1105 foreach ($tmpSpecialFields as $fk => $val) {
1106 $menuItems[$c][$fk] = $val;
1107 }
1108 }
1109 $c++;
1110 }
1111 }
1112 }
1113 return $menuItems;
1114 }
1115
1116 /**
1117 * Analyzes the parameters to find if the link needs a cHash parameter.
1118 *
1119 * @param string $queryString
1120 * @return void
1121 */
1122 protected function analyzeCacheHashRequirements($queryString) {
1123 $parameters = GeneralUtility::explodeUrl2Array($queryString);
1124 if (count($parameters) > 0) {
1125 $cacheHashCalculator = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\CacheHashCalculator::class);
1126 /** @var \TYPO3\CMS\Frontend\Page\CacheHashCalculator $cacheHashCalculator */
1127 $cHashParameters = $cacheHashCalculator->getRelevantParameters($queryString);
1128 if (count($cHashParameters) > 1) {
1129 $this->useCacheHash = (
1130 $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ||
1131 !isset($parameters['no_cache']) ||
1132 !$parameters['no_cache']
1133 );
1134 }
1135 }
1136 }
1137
1138 /**
1139 * Checks if a page is OK to include in the final menu item array. Pages can be excluded if the doktype is wrong, if they are hidden in navigation, have a uid in the list of banned uids etc.
1140 *
1141 * @param array $data Array of menu items
1142 * @param array $banUidArray Array of page uids which are to be excluded
1143 * @param bool $spacer If set, then the page is a spacer.
1144 * @return bool Returns TRUE if the page can be safely included.
1145 */
1146 public function filterMenuPages(&$data, $banUidArray, $spacer) {
1147 $includePage = TRUE;
1148 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'])) {
1149 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] as $classRef) {
1150 $hookObject = GeneralUtility::getUserObj($classRef);
1151 if (!$hookObject instanceof \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuFilterPagesHookInterface) {
1152 throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuFilterPagesHookInterface::class, 1269877402);
1153 }
1154 $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $spacer, $this);
1155 }
1156 }
1157 if (!$includePage) {
1158 return FALSE;
1159 }
1160 if ($data['_SAFE']) {
1161 return TRUE;
1162 }
1163 $uid = $data['uid'];
1164 // If the spacer-function is not enabled, spacers will not enter the $menuArr
1165 if ($this->mconf['SPC'] || !$spacer) {
1166 // Page may not be 'not_in_menu' or 'Backend User Section'
1167 if (!GeneralUtility::inList($this->doktypeExcludeList, $data['doktype'])) {
1168 // Not hidden in navigation
1169 if (!$data['nav_hide'] || $this->conf['includeNotInMenu']) {
1170 // not in banned uid's
1171 if (!GeneralUtility::inArray($banUidArray, $uid)) {
1172 // Checks if the default language version can be shown:
1173 // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
1174 $blockPage = $data['l18n_cfg'] & 1 && (!$GLOBALS['TSFE']->sys_language_uid || $GLOBALS['TSFE']->sys_language_uid && !$data['_PAGES_OVERLAY']);
1175 if (!$blockPage) {
1176 // Checking if a page should be shown in the menu depending on whether a translation exists:
1177 $tok = TRUE;
1178 // There is an alternative language active AND the current page requires a translation:
1179 if ($GLOBALS['TSFE']->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1180 if (!$data['_PAGES_OVERLAY']) {
1181 $tok = FALSE;
1182 }
1183 }
1184 // Continue if token is TRUE:
1185 if ($tok) {
1186 // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1187 if ($this->conf['protectLvar']) {
1188 $languageUid = (int)$GLOBALS['TSFE']->config['config']['sys_language_uid'];
1189 if ($languageUid && ($this->conf['protectLvar'] == 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
1190 $olRec = $GLOBALS['TSFE']->sys_page->getPageOverlay($data['uid'], $languageUid);
1191 if (!count($olRec)) {
1192 // If no pages_language_overlay record then page can NOT be accessed in the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1193 $data['_ADD_GETVARS'] .= '&L=0';
1194 }
1195 }
1196 }
1197 return TRUE;
1198 }
1199 }
1200 }
1201 }
1202 }
1203 }
1204 }
1205
1206 /**
1207 * Generating the per-menu-item configuration arrays based on the settings for item states (NO, RO, ACT, CUR etc) set in ->mconf (config for the current menu object)
1208 * Basically it will produce an individual array for each menu item based on the item states. BUT in addition the "optionSplit" syntax for the values is ALSO evaluated here so that all property-values are "option-splitted" and the output will thus be resolved.
1209 * Is called from the "generate" functions in the extension classes. The function is processor intensive due to the option split feature in particular. But since the generate function is not always called (since the ->result array may be cached, see makeMenu) it doesn't hurt so badly.
1210 *
1211 * @param int $splitCount Number of menu items in the menu
1212 * @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)
1213 * @access private
1214 */
1215 public function procesItemStates($splitCount) {
1216 // Prepare normal settings
1217 if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1218 // Setting a blank array if NO=1 and there are no properties.
1219 $this->mconf['NO.'] = array();
1220 }
1221 $NOconf = $this->tmpl->splitConfArray($this->mconf['NO.'], $splitCount);
1222 // Prepare rollOver settings, overriding normal settings
1223 $ROconf = array();
1224 if ($this->mconf['RO']) {
1225 $ROconf = $this->tmpl->splitConfArray($this->mconf['RO.'], $splitCount);
1226 }
1227 // Prepare IFSUB settings, overriding normal settings
1228 // IFSUB is TRUE if there exist submenu items to the current item
1229 if ($this->mconf['IFSUB']) {
1230 // Flag: If $IFSUB is generated
1231 $IFSUBinit = 0;
1232 foreach ($NOconf as $key => $val) {
1233 if ($this->isItemState('IFSUB', $key)) {
1234 // if this is the first IFSUB element, we must generate IFSUB.
1235 if (!$IFSUBinit) {
1236 $IFSUBconf = $this->tmpl->splitConfArray($this->mconf['IFSUB.'], $splitCount);
1237 if ($this->mconf['IFSUBRO']) {
1238 $IFSUBROconf = $this->tmpl->splitConfArray($this->mconf['IFSUBRO.'], $splitCount);
1239 }
1240 $IFSUBinit = 1;
1241 }
1242 // Substitute normal with ifsub
1243 $NOconf[$key] = $IFSUBconf[$key];
1244 // If rollOver on normal, we must apply a state for rollOver on the active
1245 if ($ROconf) {
1246 // If RollOver on active then apply this
1247 $ROconf[$key] = $IFSUBROconf[$key] ?: $IFSUBconf[$key];
1248 }
1249 }
1250 }
1251 }
1252 // Prepare active settings, overriding normal settings
1253 if ($this->mconf['ACT']) {
1254 // Flag: If $ACT is generated
1255 $ACTinit = 0;
1256 // Find active
1257 foreach ($NOconf as $key => $val) {
1258 if ($this->isItemState('ACT', $key)) {
1259 // If this is the first 'active', we must generate ACT.
1260 if (!$ACTinit) {
1261 $ACTconf = $this->tmpl->splitConfArray($this->mconf['ACT.'], $splitCount);
1262 // Prepare active rollOver settings, overriding normal active settings
1263 if ($this->mconf['ACTRO']) {
1264 $ACTROconf = $this->tmpl->splitConfArray($this->mconf['ACTRO.'], $splitCount);
1265 }
1266 $ACTinit = 1;
1267 }
1268 // Substitute normal with active
1269 $NOconf[$key] = $ACTconf[$key];
1270 // If rollOver on normal, we must apply a state for rollOver on the active
1271 if ($ROconf) {
1272 // If RollOver on active then apply this
1273 $ROconf[$key] = $ACTROconf[$key] ?: $ACTconf[$key];
1274 }
1275 }
1276 }
1277 }
1278 // Prepare ACT (active)/IFSUB settings, overriding normal settings
1279 // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
1280 if ($this->mconf['ACTIFSUB']) {
1281 // Flag: If $ACTIFSUB is generated
1282 $ACTIFSUBinit = 0;
1283 // Find active
1284 foreach ($NOconf as $key => $val) {
1285 if ($this->isItemState('ACTIFSUB', $key)) {
1286 // If this is the first 'active', we must generate ACTIFSUB.
1287 if (!$ACTIFSUBinit) {
1288 $ACTIFSUBconf = $this->tmpl->splitConfArray($this->mconf['ACTIFSUB.'], $splitCount);
1289 // Prepare active rollOver settings, overriding normal active settings
1290 if ($this->mconf['ACTIFSUBRO']) {
1291 $ACTIFSUBROconf = $this->tmpl->splitConfArray($this->mconf['ACTIFSUBRO.'], $splitCount);
1292 }
1293 $ACTIFSUBinit = 1;
1294 }
1295 // Substitute normal with active
1296 $NOconf[$key] = $ACTIFSUBconf[$key];
1297 // If rollOver on normal, we must apply a state for rollOver on the active
1298 if ($ROconf) {
1299 // If RollOver on active then apply this
1300 $ROconf[$key] = $ACTIFSUBROconf[$key] ?: $ACTIFSUBconf[$key];
1301 }
1302 }
1303 }
1304 }
1305 // Prepare CUR (current) settings, overriding normal settings
1306 // CUR is TRUE if the current page equals the item here!
1307 if ($this->mconf['CUR']) {
1308 // Flag: If $CUR is generated
1309 $CURinit = 0;
1310 foreach ($NOconf as $key => $val) {
1311 if ($this->isItemState('CUR', $key)) {
1312 // if this is the first 'current', we must generate CUR. Basically this control is just inherited
1313 // from the other implementations as current would only exist one time and thats it
1314 // (unless you use special-features of HMENU)
1315 if (!$CURinit) {
1316 $CURconf = $this->tmpl->splitConfArray($this->mconf['CUR.'], $splitCount);
1317 if ($this->mconf['CURRO']) {
1318 $CURROconf = $this->tmpl->splitConfArray($this->mconf['CURRO.'], $splitCount);
1319 }
1320 $CURinit = 1;
1321 }
1322 // Substitute normal with current
1323 $NOconf[$key] = $CURconf[$key];
1324 // If rollOver on normal, we must apply a state for rollOver on the active
1325 if ($ROconf) {
1326 // If RollOver on active then apply this
1327 $ROconf[$key] = $CURROconf[$key] ?: $CURconf[$key];
1328 }
1329 }
1330 }
1331 }
1332 // Prepare CUR (current)/IFSUB settings, overriding normal settings
1333 // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
1334 if ($this->mconf['CURIFSUB']) {
1335 // Flag: If $CURIFSUB is generated
1336 $CURIFSUBinit = 0;
1337 foreach ($NOconf as $key => $val) {
1338 if ($this->isItemState('CURIFSUB', $key)) {
1339 // If this is the first 'current', we must generate CURIFSUB.
1340 if (!$CURIFSUBinit) {
1341 $CURIFSUBconf = $this->tmpl->splitConfArray($this->mconf['CURIFSUB.'], $splitCount);
1342 // Prepare current rollOver settings, overriding normal current settings
1343 if ($this->mconf['CURIFSUBRO']) {
1344 $CURIFSUBROconf = $this->tmpl->splitConfArray($this->mconf['CURIFSUBRO.'], $splitCount);
1345 }
1346 $CURIFSUBinit = 1;
1347 }
1348 // Substitute normal with active
1349 $NOconf[$key] = $CURIFSUBconf[$key];
1350 // If rollOver on normal, we must apply a state for rollOver on the current
1351 if ($ROconf) {
1352 // If RollOver on current then apply this
1353 $ROconf[$key] = $CURIFSUBROconf[$key] ?: $CURIFSUBconf[$key];
1354 }
1355 }
1356 }
1357 }
1358 // Prepare active settings, overriding normal settings
1359 if ($this->mconf['USR']) {
1360 // Flag: If $USR is generated
1361 $USRinit = 0;
1362 // Find active
1363 foreach ($NOconf as $key => $val) {
1364 if ($this->isItemState('USR', $key)) {
1365 // if this is the first active, we must generate USR.
1366 if (!$USRinit) {
1367 $USRconf = $this->tmpl->splitConfArray($this->mconf['USR.'], $splitCount);
1368 // Prepare active rollOver settings, overriding normal active settings
1369 if ($this->mconf['USRRO']) {
1370 $USRROconf = $this->tmpl->splitConfArray($this->mconf['USRRO.'], $splitCount);
1371 }
1372 $USRinit = 1;
1373 }
1374 // Substitute normal with active
1375 $NOconf[$key] = $USRconf[$key];
1376 // If rollOver on normal, we must apply a state for rollOver on the active
1377 if ($ROconf) {
1378 // If RollOver on active then apply this
1379 $ROconf[$key] = $USRROconf[$key] ?: $USRconf[$key];
1380 }
1381 }
1382 }
1383 }
1384 // Prepare spacer settings, overriding normal settings
1385 if ($this->mconf['SPC']) {
1386 // Flag: If $SPC is generated
1387 $SPCinit = 0;
1388 // Find spacers
1389 foreach ($NOconf as $key => $val) {
1390 if ($this->isItemState('SPC', $key)) {
1391 // If this is the first spacer, we must generate SPC.
1392 if (!$SPCinit) {
1393 $SPCconf = $this->tmpl->splitConfArray($this->mconf['SPC.'], $splitCount);
1394 $SPCinit = 1;
1395 }
1396 // Substitute normal with spacer
1397 $NOconf[$key] = $SPCconf[$key];
1398 }
1399 }
1400 }
1401 // Prepare Userdefined settings
1402 if ($this->mconf['USERDEF1']) {
1403 // Flag: If $USERDEF1 is generated
1404 $USERDEF1init = 0;
1405 // Find active
1406 foreach ($NOconf as $key => $val) {
1407 if ($this->isItemState('USERDEF1', $key)) {
1408 // If this is the first active, we must generate USERDEF1.
1409 if (!$USERDEF1init) {
1410 $USERDEF1conf = $this->tmpl->splitConfArray($this->mconf['USERDEF1.'], $splitCount);
1411 // Prepare active rollOver settings, overriding normal active settings
1412 if ($this->mconf['USERDEF1RO']) {
1413 $USERDEF1ROconf = $this->tmpl->splitConfArray($this->mconf['USERDEF1RO.'], $splitCount);
1414 }
1415 $USERDEF1init = 1;
1416 }
1417 // Substitute normal with active
1418 $NOconf[$key] = $USERDEF1conf[$key];
1419 // If rollOver on normal, we must apply a state for rollOver on the active
1420 if ($ROconf) {
1421 // If RollOver on active then apply this
1422 $ROconf[$key] = $USERDEF1ROconf[$key] ?: $USERDEF1conf[$key];
1423 }
1424 }
1425 }
1426 }
1427 // Prepare Userdefined settings
1428 if ($this->mconf['USERDEF2']) {
1429 // Flag: If $USERDEF2 is generated
1430 $USERDEF2init = 0;
1431 // Find active
1432 foreach ($NOconf as $key => $val) {
1433 if ($this->isItemState('USERDEF2', $key)) {
1434 // If this is the first active, we must generate USERDEF2.
1435 if (!$USERDEF2init) {
1436 $USERDEF2conf = $this->tmpl->splitConfArray($this->mconf['USERDEF2.'], $splitCount);
1437 // Prepare active rollOver settings, overriding normal active settings
1438 if ($this->mconf['USERDEF2RO']) {
1439 $USERDEF2ROconf = $this->tmpl->splitConfArray($this->mconf['USERDEF2RO.'], $splitCount);
1440 }
1441 $USERDEF2init = 1;
1442 }
1443 // Substitute normal with active
1444 $NOconf[$key] = $USERDEF2conf[$key];
1445 // If rollOver on normal, we must apply a state for rollOver on the active
1446 if ($ROconf) {
1447 // If RollOver on active then apply this
1448 $ROconf[$key] = $USERDEF2ROconf[$key] ?: $USERDEF2conf[$key];
1449 }
1450 }
1451 }
1452 }
1453 return array($NOconf, $ROconf);
1454 }
1455
1456 /**
1457 * 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
1458 * This function doesn't care about the url, because if we let the url be redirected, it will be logged in the stat!!!
1459 *
1460 * @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)
1461 * @param string $altTarget Alternative target
1462 * @param int $typeOverride Alternative type
1463 * @return array Returns an array with A-tag attributes as key/value pairs (HREF, TARGET and onClick)
1464 * @access private
1465 */
1466 public function link($key, $altTarget = '', $typeOverride = '') {
1467 // Mount points:
1468 $MP_var = $this->getMPvar($key);
1469 $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1470 // Setting override ID
1471 if ($this->mconf['overrideId'] || $this->menuArr[$key]['overrideId']) {
1472 $overrideArray = array();
1473 // If a user script returned the value overrideId in the menu array we use that as page id
1474 $overrideArray['uid'] = $this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId'];
1475 $overrideArray['alias'] = '';
1476 // Clear MP parameters since ID was changed.
1477 $MP_params = '';
1478 } else {
1479 $overrideArray = '';
1480 }
1481 // Setting main target:
1482 if ($altTarget) {
1483 $mainTarget = $altTarget;
1484 } elseif ($this->mconf['target.']) {
1485 $mainTarget = $this->parent_cObj->stdWrap($this->mconf['target'], $this->mconf['target.']);
1486 } else {
1487 $mainTarget = $this->mconf['target'];
1488 }
1489 // Creating link:
1490 if ($this->mconf['collapse'] && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1491 $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1492 $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', $overrideArray, $this->mconf['addParams'] . $MP_params . $this->menuArr[$key]['_ADD_GETVARS'], $typeOverride);
1493 } else {
1494 $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, '', '', $overrideArray, $this->mconf['addParams'] . $MP_params . $this->I['val']['additionalParams'] . $this->menuArr[$key]['_ADD_GETVARS'], $typeOverride);
1495 }
1496 // Override URL if using "External URL" as doktype with a valid e-mail address:
1497 if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_LINK && $this->menuArr[$key]['urltype'] == 3 && GeneralUtility::validEmail($this->menuArr[$key]['url'])) {
1498 // Create mailto-link using \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink (concerning spamProtectEmailAddresses):
1499 $LD['totalURL'] = $this->parent_cObj->typoLink_URL(array('parameter' => $this->menuArr[$key]['url']));
1500 $LD['target'] = '';
1501 }
1502
1503 // Override url if current page is a shortcut
1504 $shortcut = NULL;
1505 if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_SHORTCUT && $this->menuArr[$key]['shortcut_mode'] != PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1506
1507 $menuItem = $this->determineOriginalShortcutPage($this->menuArr[$key]);
1508
1509 try {
1510 $shortcut = $GLOBALS['TSFE']->getPageShortcut(
1511 $menuItem['shortcut'],
1512 $menuItem['shortcut_mode'],
1513 $menuItem['uid'],
1514 20,
1515 array(),
1516 TRUE
1517 );
1518 } catch (\Exception $ex) {
1519
1520 }
1521 if (!is_array($shortcut)) {
1522 return array();
1523 }
1524 // Only setting url, not target
1525 $LD['totalURL'] = $this->parent_cObj->typoLink_URL(array(
1526 'parameter' => $shortcut['uid'],
1527 'additionalParams' => $this->mconf['addParams'] . $MP_params . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1528 'linkAccessRestrictedPages' => $this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE'
1529 ));
1530 }
1531 if ($shortcut) {
1532 $pageData = $shortcut;
1533 $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1534 } else {
1535 $pageData = $this->menuArr[$key];
1536 }
1537 // Manipulation in case of access restricted pages:
1538 $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1539 // Overriding URL / Target if set to do so:
1540 if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
1541 $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1542 if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1543 $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1544 }
1545 }
1546 // OnClick open in windows.
1547 $onClick = '';
1548 if ($this->mconf['JSWindow']) {
1549 $conf = $this->mconf['JSWindow.'];
1550 $url = $LD['totalURL'];
1551 $LD['totalURL'] = '#';
1552 $onClick = 'openPic(\'' . $GLOBALS['TSFE']->baseUrlWrap($url) . '\',\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\',\'' . $conf['params'] . '\'); return false;';
1553 $GLOBALS['TSFE']->setJS('openPic');
1554 }
1555 // look for type and popup
1556 // following settings are valid in field target:
1557 // 230 will add type=230 to the link
1558 // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1559 // 230 _blank will add type=230 to the link and open with target "_blank"
1560 // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1561 $matches = array();
1562 $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : FALSE;
1563 if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) || $targetIsType) {
1564 // has type?
1565 if ((int)$matches[1] || $targetIsType) {
1566 $LD['totalURL'] = $this->parent_cObj->URLqMark($LD['totalURL'], '&type=' . ($targetIsType ?: (int)$matches[1]));
1567 $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1568 }
1569 // Open in popup window?
1570 if ($matches[3] && $matches[4]) {
1571 $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1572 $onClick = 'vHWin=window.open('
1573 . GeneralUtility::quoteJSvalue($GLOBALS['TSFE']->baseUrlWrap($LD['totalURL']))
1574 . ',\'FEopenLink\',\'' . $JSparamWH . '\');vHWin.focus();return false;';
1575 $LD['target'] = '';
1576 }
1577 }
1578 // out:
1579 $list = array();
1580 // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1581 // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1582 // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1583 $list['HREF'] = strlen($LD['totalURL']) ? $LD['totalURL'] : $GLOBALS['TSFE']->baseUrl;
1584 $list['TARGET'] = $LD['target'];
1585 $list['onClick'] = $onClick;
1586 return $list;
1587 }
1588
1589 /**
1590 * Determines original shortcut destination in page overlays.
1591 *
1592 * Since the pages records used for menu rendering are overlaid by default,
1593 * the original 'shortcut' value is lost, if a translation did not define one.
1594 * The behaviour in TSFE can be compared to the 'mergeIfNotBlank' feature, but
1595 * it's hardcoded there and not related to the mentioned setting at all.
1596 *
1597 * @param array $page
1598 * @return array
1599 * @todo Once the page_language_overlay behaviour was removed, this method can be removed again
1600 */
1601 protected function determineOriginalShortcutPage(array $page) {
1602 // Check if modification is required
1603 if (
1604 $GLOBALS['TSFE']->sys_language_uid > 0
1605 && empty($page['shortcut'])
1606 && !empty($page['uid'])
1607 && !empty($page['_PAGES_OVERLAY'])
1608 && !empty($page['_PAGES_OVERLAY_UID'])
1609 ) {
1610 // Using raw record since the record was overlaid and is correct already:
1611 $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1612
1613 if (!empty($originalPage['shortcut'])) {
1614 $page['shortcut'] = $originalPage['shortcut'];
1615 }
1616 }
1617
1618 return $page;
1619 }
1620
1621 /**
1622 * Will change $LD (passed by reference) if the page is access restricted
1623 *
1624 * @param array $LD The array from the linkData() function
1625 * @param array $page Page array
1626 * @param string $mainTarget Main target value
1627 * @param string $typeOverride Type number override if any
1628 * @return void ($LD passed by reference might be changed.)
1629 */
1630 public function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride) {
1631 // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1632 if ($this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$GLOBALS['TSFE']->checkPageGroupAccess($page)) {
1633 $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1634 $addParams = str_replace(
1635 array(
1636 '###RETURN_URL###',
1637 '###PAGE_ID###'
1638 ),
1639 array(
1640 rawurlencode($LD['totalURL']),
1641 isset($page['_SHORTCUT_PAGE_UID']) ? $page['_SHORTCUT_PAGE_UID'] : $page['uid']
1642 ),
1643 $this->mconf['showAccessRestrictedPages.']['addParams']
1644 );
1645 $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', '', $addParams, $typeOverride);
1646 }
1647 }
1648
1649 /**
1650 * Creates a submenu level to the current level - if configured for.
1651 *
1652 * @param int $uid Page id of the current page for which a submenu MAY be produced (if conditions are met)
1653 * @param string $objSuffix Object prefix, see ->start()
1654 * @return string HTML content of the submenu
1655 * @access private
1656 */
1657 public function subMenu($uid, $objSuffix = '') {
1658 // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1659 $altArray = '';
1660 if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU']) && count($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1661 $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1662 }
1663 // Make submenu if the page is the next active
1664 $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix];
1665 // stdWrap for expAll
1666 if (isset($this->mconf['expAll.'])) {
1667 $this->mconf['expAll'] = $this->parent_cObj->stdWrap($this->mconf['expAll'], $this->mconf['expAll.']);
1668 }
1669 if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !$this->mconf['sectionIndex']) {
1670 try {
1671 $menuObjectFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\Menu\MenuContentObjectFactory::class);
1672 $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1673 $submenu->entryLevel = $this->entryLevel + 1;
1674 $submenu->rL_uidRegister = $this->rL_uidRegister;
1675 $submenu->MP_array = $this->MP_array;
1676 if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1677 $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1678 }
1679 // Especially scripts that build the submenu needs the parent data
1680 $submenu->parent_cObj = $this->parent_cObj;
1681 $submenu->parentMenuArr = $this->menuArr;
1682 // Setting alternativeMenuTempArray (will be effective only if an array)
1683 if (is_array($altArray)) {
1684 $submenu->alternativeMenuTempArray = $altArray;
1685 }
1686 if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1687 $submenu->makeMenu();
1688 // Memorize the current menu item count
1689 $tempCountMenuObj = $GLOBALS['TSFE']->register['count_MENUOBJ'];
1690 // Reset the menu item count for the submenu
1691 $GLOBALS['TSFE']->register['count_MENUOBJ'] = 0;
1692 $content = $submenu->writeMenu();
1693 // Restore the item count now that the submenu has been handled
1694 $GLOBALS['TSFE']->register['count_MENUOBJ'] = $tempCountMenuObj;
1695 $GLOBALS['TSFE']->register['count_menuItems'] = count($this->menuArr);
1696 return $content;
1697 }
1698 } catch (\TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException $e) {
1699 }
1700 }
1701 }
1702
1703 /**
1704 * Returns TRUE if the page with UID $uid is the NEXT page in root line (which means a submenu should be drawn)
1705 *
1706 * @param int $uid Page uid to evaluate.
1707 * @param string $MPvar MPvar for the current position of item.
1708 * @return bool TRUE if page with $uid is active
1709 * @access private
1710 * @see subMenu()
1711 */
1712 public function isNext($uid, $MPvar = '') {
1713 // Check for always active PIDs:
1714 if (count($this->alwaysActivePIDlist) && in_array($uid, $this->alwaysActivePIDlist)) {
1715 return TRUE;
1716 }
1717 $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1718 if ($uid && $testUid == $this->nextActive) {
1719 return TRUE;
1720 }
1721 }
1722
1723 /**
1724 * Returns TRUE if the page with UID $uid is active (in the current rootline)
1725 *
1726 * @param int $uid Page uid to evaluate.
1727 * @param string $MPvar MPvar for the current position of item.
1728 * @return bool TRUE if page with $uid is active
1729 * @access private
1730 */
1731 public function isActive($uid, $MPvar = '') {
1732 // Check for always active PIDs:
1733 if (count($this->alwaysActivePIDlist) && in_array($uid, $this->alwaysActivePIDlist)) {
1734 return TRUE;
1735 }
1736 $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1737 if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister)) {
1738 return TRUE;
1739 }
1740 }
1741
1742 /**
1743 * Returns TRUE if the page with UID $uid is the CURRENT page (equals $GLOBALS['TSFE']->id)
1744 *
1745 * @param int $uid Page uid to evaluate.
1746 * @param string $MPvar MPvar for the current position of item.
1747 * @return bool TRUE if page $uid = $GLOBALS['TSFE']->id
1748 * @access private
1749 */
1750 public function isCurrent($uid, $MPvar = '') {
1751 $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1752 if ($uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid) {
1753 return TRUE;
1754 }
1755 }
1756
1757 /**
1758 * Returns TRUE if there is a submenu with items for the page id, $uid
1759 * Used by the item states "IFSUB", "ACTIFSUB" and "CURIFSUB" to check if there is a submenu
1760 *
1761 * @param int $uid Page uid for which to search for a submenu
1762 * @return bool Returns TRUE if there was a submenu with items found
1763 * @access private
1764 */
1765 public function isSubMenu($uid) {
1766 // Looking for a mount-pid for this UID since if that
1767 // exists we should look for a subpages THERE and not in the input $uid;
1768 $mount_info = $this->sys_page->getMountPointInfo($uid);
1769 if (is_array($mount_info)) {
1770 $uid = $mount_info['mount_pid'];
1771 }
1772 $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1773 $hasSubPages = FALSE;
1774 $bannedUids = $this->getBannedUids();
1775 foreach ($recs as $theRec) {
1776 // no valid subpage if the document type is excluded from the menu
1777 if (GeneralUtility::inList($this->doktypeExcludeList, $theRec['doktype'])) {
1778 continue;
1779 }
1780 // No valid subpage if the page is hidden inside menus and
1781 // it wasn't forced to show such entries
1782 if ($theRec['nav_hide'] && !$this->conf['includeNotInMenu']) {
1783 continue;
1784 }
1785 // No valid subpage if the default language should be shown and the page settings
1786 // are excluding the visibility of the default language
1787 if (!$GLOBALS['TSFE']->sys_language_uid && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
1788 continue;
1789 }
1790 // No valid subpage if the alternative language should be shown and the page settings
1791 // are requiring a valid overlay but it doesn't exists
1792 $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg']);
1793 if ($GLOBALS['TSFE']->sys_language_uid && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1794 continue;
1795 }
1796 // No valid subpage if the subpage is banned by excludeUidList
1797 if (in_array($theRec['uid'], $bannedUids)) {
1798 continue;
1799 }
1800 $hasSubPages = TRUE;
1801 break;
1802 }
1803 return $hasSubPages;
1804 }
1805
1806 /**
1807 * Used by procesItemStates() to evaluate if a menu item (identified by $key) is in a certain state.
1808 *
1809 * @param string $kind The item state to evaluate (SPC, IFSUB, ACT etc... but no xxxRO states of course)
1810 * @param int $key Key pointing to menu item from ->menuArr
1811 * @return bool True (integer!=0) if match, otherwise FALSE (=0, zero)
1812 * @access private
1813 * @see procesItemStates()
1814 */
1815 public function isItemState($kind, $key) {
1816 $natVal = 0;
1817 // If any value is set for ITEM_STATE the normal evaluation is discarded
1818 if ($this->menuArr[$key]['ITEM_STATE']) {
1819 if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1820 $natVal = 1;
1821 }
1822 } else {
1823 switch ($kind) {
1824 case 'SPC':
1825 $natVal = $this->menuArr[$key]['isSpacer'];
1826 break;
1827 case 'IFSUB':
1828 $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1829 break;
1830 case 'ACT':
1831 $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1832 break;
1833 case 'ACTIFSUB':
1834 $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1835 break;
1836 case 'CUR':
1837 $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
1838 break;
1839 case 'CURIFSUB':
1840 $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1841 break;
1842 case 'USR':
1843 $natVal = $this->menuArr[$key]['fe_group'];
1844 break;
1845 }
1846 }
1847 return $natVal;
1848 }
1849
1850 /**
1851 * Creates an access-key for a TMENU/GMENU menu item based on the menu item titles first letter
1852 *
1853 * @param string $title Menu item title.
1854 * @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
1855 * @access private
1856 */
1857 public function accessKey($title) {
1858 // The global array ACCESSKEY is used to globally control if letters are already used!!
1859 $result = array();
1860 $title = trim(strip_tags($title));
1861 $titleLen = strlen($title);
1862 for ($a = 0; $a < $titleLen; $a++) {
1863 $key = strtoupper(substr($title, $a, 1));
1864 if (preg_match('/[A-Z]/', $key) && !isset($GLOBALS['TSFE']->accessKey[$key])) {
1865 $GLOBALS['TSFE']->accessKey[$key] = 1;
1866 $result['code'] = ' accesskey="' . $key . '"';
1867 $result['alt'] = ' (ALT+' . $key . ')';
1868 $result['key'] = $key;
1869 break;
1870 }
1871 }
1872 return $result;
1873 }
1874
1875 /**
1876 * Calls a user function for processing of internal data.
1877 * Used for the properties "IProcFunc" and "itemArrayProcFunc"
1878 *
1879 * @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".
1880 * @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
1881 * @return mixed The processed $passVar
1882 * @access private
1883 */
1884 public function userProcess($mConfKey, $passVar) {
1885 if ($this->mconf[$mConfKey]) {
1886 $funcConf = $this->mconf[$mConfKey . '.'];
1887 $funcConf['parentObj'] = $this;
1888 $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
1889 }
1890 return $passVar;
1891 }
1892
1893 /**
1894 * 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'])
1895 *
1896 * @return void
1897 * @access private
1898 */
1899 public function setATagParts() {
1900 $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
1901 $params = $params !== '' ? ' ' . $params : '';
1902 $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], 1) . $params . '>';
1903 $this->I['A2'] = '</a>';
1904 }
1905
1906 /**
1907 * Returns the title for the navigation
1908 *
1909 * @param string $title The current page title
1910 * @param string $nav_title The current value of the navigation title
1911 * @return string Returns the navigation title if it is NOT blank, otherwise the page title.
1912 * @access private
1913 */
1914 public function getPageTitle($title, $nav_title) {
1915 return trim($nav_title) !== '' ? $nav_title : $title;
1916 }
1917
1918 /**
1919 * Return MPvar string for entry $key in ->menuArr
1920 *
1921 * @param int $key Pointer to element in ->menuArr
1922 * @return string MP vars for element.
1923 * @see link()
1924 */
1925 public function getMPvar($key) {
1926 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
1927 $localMP_array = $this->MP_array;
1928 // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
1929 if ($this->menuArr[$key]['_MP_PARAM']) {
1930 $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
1931 }
1932 $MP_params = count($localMP_array) ? implode(',', $localMP_array) : '';
1933 return $MP_params;
1934 }
1935 }
1936
1937 /**
1938 * Returns where clause part to exclude 'not in menu' pages
1939 *
1940 * @return string where clause part.
1941 * @access private
1942 */
1943 public function getDoktypeExcludeWhere() {
1944 return $this->doktypeExcludeList ? ' AND pages.doktype NOT IN (' . $this->doktypeExcludeList . ')' : '';
1945 }
1946
1947 /**
1948 * Returns an array of banned UIDs (from excludeUidList)
1949 *
1950 * @return array Array of banned UIDs
1951 * @access private
1952 */
1953 public function getBannedUids() {
1954 $excludeUidList = isset($this->conf['excludeUidList.'])
1955 ? $this->parent_cObj->stdWrap($this->conf['excludeUidList'], $this->conf['excludeUidList.'])
1956 : $this->conf['excludeUidList'];
1957
1958 if (!trim($excludeUidList)) {
1959 return array();
1960 }
1961
1962 $banUidList = str_replace('current', $GLOBALS['TSFE']->page['uid'], $excludeUidList);
1963 return GeneralUtility::intExplode(',', $banUidList);
1964 }
1965
1966 /**
1967 * Calls typolink to create menu item links.
1968 *
1969 * @param array $page Page record (uid points where to link to)
1970 * @param string $oTarget Target frame/window
1971 * @param bool $no_cache TRUE if caching should be disabled
1972 * @param string $script Alternative script name
1973 * @param array $overrideArray Array to override values in $page
1974 * @param string $addParams Parameters to add to URL
1975 * @param array $typeOverride "type" value
1976 * @return array See linkData
1977 */
1978 public function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '') {
1979 $conf = array(
1980 'parameter' => is_array($overrideArray) && $overrideArray['uid'] ? $overrideArray['uid'] : $page['uid']
1981 );
1982 if ($typeOverride && MathUtility::canBeInterpretedAsInteger($typeOverride)) {
1983 $conf['parameter'] .= ',' . $typeOverride;
1984 }
1985 if ($addParams) {
1986 $conf['additionalParams'] = $addParams;
1987 }
1988 if ($no_cache) {
1989 $conf['no_cache'] = TRUE;
1990 } elseif ($this->useCacheHash) {
1991 $conf['useCacheHash'] = TRUE;
1992 }
1993 if ($oTarget) {
1994 $conf['target'] = $oTarget;
1995 }
1996 if ($page['sectionIndex_uid']) {
1997 $conf['section'] = $page['sectionIndex_uid'];
1998 }
1999 $conf['linkAccessRestrictedPages'] = $this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE';
2000 $this->parent_cObj->typoLink('|', $conf);
2001 $LD = $this->parent_cObj->lastTypoLinkLD;
2002 $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
2003 return $LD;
2004 }
2005
2006 /**
2007 * Generates a list of content objects with sectionIndex enabled
2008 * available on a specific page
2009 *
2010 * Used for menus with sectionIndex enabled
2011 *
2012 * @param string $altSortField Alternative sorting field
2013 * @param int $pid The page id to search for sections
2014 * @throws UnexpectedValueException if the query to fetch the content elements unexpectedly fails
2015 * @return array
2016 */
2017 protected function sectionIndex($altSortField, $pid = NULL) {
2018 $pid = (int)($pid ?: $this->id);
2019 $basePageRow = $this->sys_page->getPage($pid);
2020 if (!is_array($basePageRow)) {
2021 return array();
2022 }
2023 $configuration = $this->mconf['sectionIndex.'];
2024 $useColPos = 0;
2025 if (trim($configuration['useColPos']) !== '' || is_array($configuration['useColPos.'])) {
2026 $useColPos = $GLOBALS['TSFE']->cObj->stdWrap($configuration['useColPos'], $configuration['useColPos.']);
2027 $useColPos = (int)$useColPos;
2028 }
2029 $selectSetup = array(
2030 'pidInList' => $pid,
2031 'orderBy' => $altSortField,
2032 'languageField' => 'sys_language_uid',
2033 'where' => $useColPos >= 0 ? 'colPos=' . $useColPos : ''
2034 );
2035 $resource = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
2036 if (!$resource) {
2037 $message = 'SectionIndex: Query to fetch the content elements failed!';
2038 throw new \UnexpectedValueException($message, 1337334849);
2039 }
2040 $result = array();
2041 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resource)) {
2042 $this->sys_page->versionOL('tt_content', $row);
2043 if ($GLOBALS['TSFE']->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
2044 $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $GLOBALS['TSFE']->sys_language_contentOL);
2045 }
2046 if ($this->mconf['sectionIndex.']['type'] !== 'all') {
2047 $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
2048 $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
2049 $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
2050 if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
2051 continue;
2052 }
2053 }
2054 if (is_array($row)) {
2055 $uid = $row['uid'];
2056 $result[$uid] = $basePageRow;
2057 $result[$uid]['title'] = $row['header'];
2058 $result[$uid]['nav_title'] = $row['header'];
2059 $result[$uid]['subtitle'] = $row['subheader'];
2060 $result[$uid]['starttime'] = $row['starttime'];
2061 $result[$uid]['endtime'] = $row['endtime'];
2062 $result[$uid]['fe_group'] = $row['fe_group'];
2063 $result[$uid]['media'] = $row['media'];
2064 $result[$uid]['header_layout'] = $row['header_layout'];
2065 $result[$uid]['bodytext'] = $row['bodytext'];
2066 $result[$uid]['image'] = $row['image'];
2067 $result[$uid]['sectionIndex_uid'] = $uid;
2068 }
2069 }
2070 $GLOBALS['TYPO3_DB']->sql_free_result($resource);
2071 return $result;
2072 }
2073
2074 /**
2075 * Returns the sys_page object
2076 *
2077 * @return \TYPO3\CMS\Frontend\Page\PageRepository
2078 */
2079 public function getSysPage() {
2080 return $this->sys_page;
2081 }
2082
2083 /**
2084 * Returns the parent content object
2085 *
2086 * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
2087 */
2088 public function getParentContentObject() {
2089 return $this->parent_cObj;
2090 }
2091
2092 }