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