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