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