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