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