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