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