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