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