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