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