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