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