Passed
Branch master (6c65a4)
by Christian
27:15 queued 11:09
created

prepareMenuItemsForListMenu()   C

Complexity

Conditions 15
Paths 164

Size

Total Lines 56
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 34
nc 164
nop 1
dl 0
loc 56
rs 5.9829
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Cache\CacheManager;
18
use TYPO3\CMS\Core\Database\ConnectionPool;
19
use TYPO3\CMS\Core\Database\RelationHandler;
20
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
21
use TYPO3\CMS\Core\TypoScript\TemplateService;
22
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Core\Utility\MathUtility;
25
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
26
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
27
use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
28
use TYPO3\CMS\Frontend\Page\PageRepository;
29
30
/**
31
 * Generating navigation/menus from TypoScript
32
 *
33
 * The HMENU content object uses this (or more precisely one of the extension classes).
34
 * Among others the class generates an array of menu items. Thereafter functions from the subclasses are called.
35
 * The class is always used through extension classes (like GraphicalMenuContentObject or TextMenuContentObject).
36
 */
37
abstract class AbstractMenuContentObject
38
{
39
    /**
40
     * tells you which menu number this is. This is important when getting data from the setup
41
     *
42
     * @var int
43
     */
44
    public $menuNumber = 1;
45
46
    /**
47
     * 0 = rootFolder
48
     *
49
     * @var int
50
     */
51
    public $entryLevel = 0;
52
53
    /**
54
     * The doktype-number that defines a spacer
55
     *
56
     * @var string
57
     */
58
    public $spacerIDList = '199';
59
60
    /**
61
     * Doktypes that define which should not be included in a menu
62
     *
63
     * @var string
64
     */
65
    public $doktypeExcludeList = '6';
66
67
    /**
68
     * @var int[]
69
     */
70
    public $alwaysActivePIDlist = [];
71
72
    /**
73
     * @var string
74
     */
75
    public $imgNamePrefix = 'img';
76
77
    /**
78
     * @var int
79
     */
80
    public $imgNameNotRandom = 0;
81
82
    /**
83
     * @var bool
84
     */
85
    public $debug = false;
86
87
    /**
88
     * Loaded with the parent cObj-object when a new HMENU is made
89
     *
90
     * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
91
     */
92
    public $parent_cObj = null;
93
94
    /**
95
     * @var string
96
     */
97
    public $GMENU_fixKey = 'gmenu';
98
99
    /**
100
     * accumulation of mount point data
101
     *
102
     * @var string[]
103
     */
104
    public $MP_array = [];
105
106
    /**
107
     * HMENU configuration
108
     *
109
     * @var array
110
     */
111
    public $conf = [];
112
113
    /**
114
     * xMENU configuration (TMENU, GMENU etc)
115
     *
116
     * @var array
117
     */
118
    public $mconf = [];
119
120
    /**
121
     * @var \TYPO3\CMS\Core\TypoScript\TemplateService
122
     */
123
    public $tmpl = null;
124
125
    /**
126
     * @var \TYPO3\CMS\Frontend\Page\PageRepository
127
     */
128
    public $sys_page = null;
129
130
    /**
131
     * The base page-id of the menu.
132
     *
133
     * @var int
134
     */
135
    public $id;
136
137
    /**
138
     * Holds the page uid of the NEXT page in the root line from the page pointed to by entryLevel;
139
     * Used to expand the menu automatically if in a certain root line.
140
     *
141
     * @var string
142
     */
143
    public $nextActive;
144
145
    /**
146
     * The array of menuItems which is built
147
     *
148
     * @var array[]
149
     */
150
    public $menuArr;
151
152
    /**
153
     * @var string
154
     */
155
    public $hash;
156
157
    /**
158
     * @var array
159
     */
160
    public $result = [];
161
162
    /**
163
     * Is filled with an array of page uid numbers + RL parameters which are in the current
164
     * root line (used to evaluate whether a menu item is in active state)
165
     *
166
     * @var array
167
     */
168
    public $rL_uidRegister;
169
170
    /**
171
     * @var string
172
     */
173
    public $INPfixMD5;
174
175
    /**
176
     * @var mixed[]
177
     */
178
    public $I;
179
180
    /**
181
     * @var string
182
     */
183
    public $WMresult;
184
185
    /**
186
     * @var string
187
     */
188
    public $WMfreezePrefix;
189
190
    /**
191
     * @var int
192
     */
193
    public $WMmenuItems;
194
195
    /**
196
     * @var array[]
197
     */
198
    public $WMsubmenuObjSuffixes;
199
200
    /**
201
     * @var string
202
     */
203
    public $WMextraScript;
204
205
    /**
206
     * @var ContentObjectRenderer
207
     */
208
    public $WMcObj = null;
209
210
    /**
211
     * Can be set to contain menu item arrays for sub-levels.
212
     *
213
     * @var string
214
     */
215
    public $alternativeMenuTempArray = '';
216
217
    /**
218
     * Will be 'id' in XHTML-mode
219
     *
220
     * @var string
221
     */
222
    public $nameAttribute = 'name';
223
224
    /**
225
     * TRUE to use cHash in generated link (normally only for the language
226
     * selector and if parameters exist in the URL).
227
     *
228
     * @var bool
229
     */
230
    protected $useCacheHash = false;
231
232
    /**
233
     * Array key of the parentMenuItem in the parentMenuArr, if this menu is a subMenu.
234
     *
235
     * @var int|null
236
     */
237
    protected $parentMenuArrItemKey;
238
239
    /**
240
     * The initialization of the object. This just sets some internal variables.
241
     *
242
     * @param TemplateService $tmpl The $this->getTypoScriptFrontendController()->tmpl object
243
     * @param PageRepository $sys_page The $this->getTypoScriptFrontendController()->sys_page object
244
     * @param int|string $id A starting point page id. This should probably be blank since the 'entryLevel' value will be used then.
245
     * @param array $conf The TypoScript configuration for the HMENU cObject
246
     * @param int $menuNumber Menu number; 1,2,3. Should probably be 1
247
     * @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")
248
     * @return bool Returns TRUE on success
249
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::HMENU()
250
     */
251
    public function start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix = '')
252
    {
253
        $tsfe = $this->getTypoScriptFrontendController();
254
        // Init:
255
        $this->conf = $conf;
256
        $this->menuNumber = $menuNumber;
257
        $this->mconf = $conf[$this->menuNumber . $objSuffix . '.'];
258
        $this->debug = $tsfe->debug;
259
        $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
260
        // In XHTML and HTML5 there is no "name" attribute anymore
261
        switch ($tsfe->xhtmlDoctype) {
262
            case 'xhtml_strict':
263
                // intended fall-through
264
            case 'xhtml_11':
265
                // intended fall-through
266
            case 'html5':
267
                // intended fall-through
268
            case '':
269
                // empty means that it's HTML5 by default
270
                $this->nameAttribute = 'id';
271
                break;
272
            default:
273
                $this->nameAttribute = 'name';
274
        }
275
        // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the sys_page object
276
        if ($this->conf[$this->menuNumber . $objSuffix] && is_object($tmpl) && is_object($sys_page)) {
277
            $this->tmpl = $tmpl;
278
            $this->sys_page = $sys_page;
279
            // alwaysActivePIDlist initialized:
280
            if (trim($this->conf['alwaysActivePIDlist']) || isset($this->conf['alwaysActivePIDlist.'])) {
281
                if (isset($this->conf['alwaysActivePIDlist.'])) {
282
                    $this->conf['alwaysActivePIDlist'] = $this->parent_cObj->stdWrap(
283
                        $this->conf['alwaysActivePIDlist'],
284
                        $this->conf['alwaysActivePIDlist.']
285
                    );
286
                }
287
                $this->alwaysActivePIDlist = GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
288
            }
289
            // 'not in menu' doktypes
290
            if ($this->conf['excludeDoktypes']) {
291
                $this->doktypeExcludeList = implode(',', GeneralUtility::intExplode(',', $this->conf['excludeDoktypes']));
292
            }
293
            // EntryLevel
294
            $this->entryLevel = $this->parent_cObj->getKey(
295
                isset($conf['entryLevel.']) ? $this->parent_cObj->stdWrap(
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $this->paren...) : $conf['entryLevel'] can also be of type string; however, parameter $key of TYPO3\CMS\Frontend\Conte...bjectRenderer::getKey() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

295
                /** @scrutinizer ignore-type */ isset($conf['entryLevel.']) ? $this->parent_cObj->stdWrap(
Loading history...
296
                    $conf['entryLevel'],
297
                    $conf['entryLevel.']
298
                ) : $conf['entryLevel'],
299
                $this->tmpl->rootLine
300
            );
301
            // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
302
            // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
303
            if ($id) {
304
                $this->id = (int)$id;
305
            } else {
306
                // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
307
                $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
308
                // Traverse rootline to build MP_array of pages BEFORE the entryLevel
309
                // (MP var for ->id is picked up in the next part of the code...)
310
                foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
311
                    // For overlaid mount points, set the variable right now:
312
                    if ($levelRec['_MP_PARAM'] && $levelRec['_MOUNT_OL']) {
313
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
314
                    }
315
                    // Break when entry level is reached:
316
                    if ($entryLevel >= $this->entryLevel) {
317
                        break;
318
                    }
319
                    // For normal mount points, set the variable for next level.
320
                    if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
321
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
322
                    }
323
                }
324
            }
325
            // Return FALSE if no page ID was set (thus no menu of subpages can be made).
326
            if ($this->id <= 0) {
327
                return false;
328
            }
329
            // Check if page is a mount point, and if so set id and MP_array
330
            // (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...)
331
            $mount_info = $this->sys_page->getMountPointInfo($this->id);
332
            if (is_array($mount_info)) {
333
                $this->MP_array[] = $mount_info['MPvar'];
334
                $this->id = $mount_info['mount_pid'];
335
            }
336
            // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
337
            // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
338
            if ($this->rL_uidRegister === null) {
339
                $this->rL_uidRegister = [];
340
                $rl_MParray = [];
341
                foreach ($this->tmpl->rootLine as $v_rl) {
342
                    // For overlaid mount points, set the variable right now:
343
                    if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
344
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
345
                    }
346
                    // Add to register:
347
                    $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
348
                        (
349
                            !empty($rl_MParray)
350
                            ? ':' . implode(',', $rl_MParray)
351
                            : ''
352
                        );
353
                    // For normal mount points, set the variable for next level.
354
                    if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
355
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
356
                    }
357
                }
358
            }
359
            // Set $directoryLevel so the following evalution of the nextActive will not return
360
            // an invalid value if .special=directory was set
361
            $directoryLevel = 0;
362
            if ($this->conf['special'] === 'directory') {
363
                $value = isset($this->conf['special.']['value.']) ? $this->parent_cObj->stdWrap(
364
                    $this->conf['special.']['value'],
365
                    $this->conf['special.']['value.']
366
                ) : $this->conf['special.']['value'];
367
                if ($value === '') {
368
                    $value = $tsfe->page['uid'];
369
                }
370
                $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
371
            }
372
            // 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
373
            // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
374
            $startLevel = $directoryLevel ?: $this->entryLevel;
375
            $currentLevel = $startLevel + $this->menuNumber;
376
            if (is_array($this->tmpl->rootLine[$currentLevel])) {
377
                $nextMParray = $this->MP_array;
378
                if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
379
                    // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
380
                    // otherwise automatic expansion will not work
381
                    $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
382
                    if (isset($parentRecord['_MP_PARAM'])) {
383
                        $nextMParray[] = $parentRecord['_MP_PARAM'];
384
                    }
385
                }
386
                // In overlay mode, add next level MPvars as well:
387
                if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
388
                    $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
389
                }
390
                $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
391
                    (
392
                        !empty($nextMParray)
393
                        ? ':' . implode(',', $nextMParray)
394
                        : ''
395
                    );
396
            } else {
397
                $this->nextActive = '';
398
            }
399
            // imgNamePrefix
400
            if ($this->mconf['imgNamePrefix']) {
401
                $this->imgNamePrefix = $this->mconf['imgNamePrefix'];
402
            }
403
            $this->imgNameNotRandom = $this->mconf['imgNameNotRandom'];
404
            $retVal = true;
405
        } else {
406
            $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
407
            $retVal = false;
408
        }
409
        return $retVal;
410
    }
411
412
    /**
413
     * Creates the menu in the internal variables, ready for output.
414
     * Basically this will read the page records needed and fill in the internal $this->menuArr
415
     * Based on a hash of this array and some other variables the $this->result variable will be
416
     * loaded either from cache OR by calling the generate() method of the class to create the menu for real.
417
     */
418
    public function makeMenu()
419
    {
420
        if (!$this->id) {
421
            return;
422
        }
423
424
        $this->useCacheHash = false;
425
426
        // Initializing showAccessRestrictedPages
427
        $SAVED_where_groupAccess = '';
428
        if ($this->mconf['showAccessRestrictedPages']) {
429
            // SAVING where_groupAccess
430
            $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
431
            // Temporarily removing fe_group checking!
432
            $this->sys_page->where_groupAccess = '';
433
        }
434
435
        $menuItems = $this->prepareMenuItems();
436
437
        $c = 0;
438
        $c_b = 0;
439
        $minItems = (int)($this->mconf['minItems'] ?: $this->conf['minItems']);
440
        $maxItems = (int)($this->mconf['maxItems'] ?: $this->conf['maxItems']);
441
        $begin = $this->parent_cObj->calc($this->mconf['begin'] ? $this->mconf['begin'] : $this->conf['begin']);
442
        $minItemsConf = $this->mconf['minItems.'] ?? $this->conf['minItems.'] ?? null;
443
        $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
444
        $maxItemsConf = $this->mconf['maxItems.'] ?? $this->conf['maxItems.'] ?? null;
445
        $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
446
        $beginConf = $this->mconf['begin.'] ?? $this->conf['begin.'] ?? null;
447
        $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
448
        $banUidArray = $this->getBannedUids();
449
        // Fill in the menuArr with elements that should go into the menu:
450
        $this->menuArr = [];
451
        foreach ($menuItems as $data) {
452
            $spacer = GeneralUtility::inList($this->spacerIDList, $data['doktype']) || $data['ITEM_STATE'] === 'SPC';
453
            // if item is a spacer, $spacer is set
454
            if ($this->filterMenuPages($data, $banUidArray, $spacer)) {
455
                $c_b++;
456
                // If the beginning item has been reached.
457
                if ($begin <= $c_b) {
458
                    $this->menuArr[$c] = $data;
459
                    $this->menuArr[$c]['isSpacer'] = $spacer;
460
                    $c++;
461
                    if ($maxItems && $c >= $maxItems) {
462
                        break;
463
                    }
464
                }
465
            }
466
        }
467
        // Fill in fake items, if min-items is set.
468
        if ($minItems) {
469
            while ($c < $minItems) {
470
                $this->menuArr[$c] = [
471
                    'title' => '...',
472
                    'uid' => $this->getTypoScriptFrontendController()->id
473
                ];
474
                $c++;
475
            }
476
        }
477
        //	Passing the menuArr through a user defined function:
478
        if ($this->mconf['itemArrayProcFunc']) {
479
            $this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->userProcess('item...cFunc', $this->menuArr) can also be of type string. However, the property $menuArr is declared as type array<mixed,array>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
480
        }
481
        // Setting number of menu items
482
        $this->getTypoScriptFrontendController()->register['count_menuItems'] = count($this->menuArr);
0 ignored issues
show
Bug introduced by
It seems like $this->menuArr can also be of type string; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

482
        $this->getTypoScriptFrontendController()->register['count_menuItems'] = count(/** @scrutinizer ignore-type */ $this->menuArr);
Loading history...
483
        $this->hash = md5(
484
            serialize($this->menuArr) .
485
            serialize($this->mconf) .
486
            serialize($this->tmpl->rootLine) .
487
            serialize($this->MP_array)
488
        );
489
        // Get the cache timeout:
490
        if ($this->conf['cache_period']) {
491
            $cacheTimeout = $this->conf['cache_period'];
492
        } else {
493
            $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
494
        }
495
        $cache = $this->getCache();
496
        $cachedData = $cache->get($this->hash);
497
        if (!is_array($cachedData)) {
498
            $this->generate();
499
            $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
500
        } else {
501
            $this->result = $cachedData;
502
        }
503
        // End showAccessRestrictedPages
504
        if ($this->mconf['showAccessRestrictedPages']) {
505
            // RESTORING where_groupAccess
506
            $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
507
        }
508
    }
509
510
    /**
511
     * Generates the the menu data.
512
     *
513
     * Subclasses should overwrite this method.
514
     */
515
    public function generate()
516
    {
517
    }
518
519
    /**
520
    * @return string The HTML for the menu
521
    */
522
    public function writeMenu()
523
    {
524
        return '';
525
    }
526
527
    /**
528
     * Gets an array of page rows and removes all, which are not accessible
529
     *
530
     * @param array $pages
531
     * @return array
532
     */
533
    protected function removeInaccessiblePages(array $pages)
534
    {
535
        $banned = $this->getBannedUids();
536
        $filteredPages = [];
537
        foreach ($pages as $aPage) {
538
            if ($this->filterMenuPages($aPage, $banned, $aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
539
                $filteredPages[$aPage['uid']] = $aPage;
540
            }
541
        }
542
        return $filteredPages;
543
    }
544
545
    /**
546
     * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
547
     *
548
     * @return array
549
     */
550
    protected function prepareMenuItems()
551
    {
552
        $menuItems = [];
553
        $alternativeSortingField = trim($this->mconf['alternativeSortingField']) ?: 'sorting';
554
555
        // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
556
        $additionalWhere = $this->mconf['additionalWhere'] ?? '';
557
        if (isset($this->mconf['additionalWhere.'])) {
558
            $additionalWhere = $this->parent_cObj->stdWrap($additionalWhere, $this->mconf['additionalWhere.']);
559
        }
560
561
        // ... only for the FIRST level of a HMENU
562
        if ($this->menuNumber == 1 && $this->conf['special']) {
563
            $value = isset($this->conf['special.']['value.'])
564
                ? $this->parent_cObj->stdWrap($this->conf['special.']['value'], $this->conf['special.']['value.'])
565
                : $this->conf['special.']['value'];
566
            switch ($this->conf['special']) {
567
                case 'userfunction':
568
                    $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
569
                    break;
570
                case 'language':
571
                    $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
572
                    break;
573
                case 'directory':
574
                    $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
575
                    break;
576
                case 'list':
577
                    $menuItems = $this->prepareMenuItemsForListMenu($value);
578
                    break;
579
                case 'updated':
580
                    $menuItems = $this->prepareMenuItemsForUpdatedMenu(
581
                        $value,
582
                        $this->mconf['alternativeSortingField'] ?: false
0 ignored issues
show
Bug introduced by
It seems like $this->mconf['alternativeSortingField'] ?: false can also be of type false; however, parameter $sortingField of TYPO3\CMS\Frontend\Conte...nuItemsForUpdatedMenu() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

582
                        /** @scrutinizer ignore-type */ $this->mconf['alternativeSortingField'] ?: false
Loading history...
583
                    );
584
                    break;
585
                case 'keywords':
586
                    $menuItems = $this->prepareMenuItemsForKeywordsMenu(
587
                        $value,
588
                        $this->mconf['alternativeSortingField'] ?: false
0 ignored issues
show
Bug introduced by
It seems like $this->mconf['alternativeSortingField'] ?: false can also be of type false; however, parameter $sortingField of TYPO3\CMS\Frontend\Conte...uItemsForKeywordsMenu() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

588
                        /** @scrutinizer ignore-type */ $this->mconf['alternativeSortingField'] ?: false
Loading history...
589
                    );
590
                    break;
591
                case 'categories':
592
                    /** @var CategoryMenuUtility $categoryMenuUtility */
593
                    $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
594
                    $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
595
                    break;
596
                case 'rootline':
597
                    $menuItems = $this->prepareMenuItemsForRootlineMenu();
598
                    break;
599
                case 'browse':
600
                    $menuItems = $this->prepareMenuItemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
601
                    break;
602
            }
603
            if ($this->mconf['sectionIndex']) {
604
                $sectionIndexes = [];
605
                foreach ($menuItems as $page) {
606
                    $sectionIndexes = $sectionIndexes + $this->sectionIndex($alternativeSortingField, $page['uid']);
607
                }
608
                $menuItems = $sectionIndexes;
609
            }
610
        } elseif (is_array($this->alternativeMenuTempArray)) {
611
            // Setting $menuItems array if not level 1.
612
            $menuItems = $this->alternativeMenuTempArray;
613
        } elseif ($this->mconf['sectionIndex']) {
614
            $menuItems = $this->sectionIndex($alternativeSortingField);
615
        } else {
616
            // Default: Gets a hierarchical menu based on subpages of $this->id
617
            $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
618
        }
619
        return $menuItems;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $menuItems also could return the type string which is incompatible with the documented return type array.
Loading history...
620
    }
621
622
    /**
623
     * Fetches all menuitems if special = userfunction is set
624
     *
625
     * @param string $specialValue The value from special.value
626
     * @param string $sortingField The sorting field
627
     * @return array
628
     */
629
    protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
630
    {
631
        $menuItems = $this->parent_cObj->callUserFunction(
632
            $this->conf['special.']['userFunc'],
633
            array_merge($this->conf['special.'], ['value' => $specialValue, '_altSortField' => $sortingField]),
634
            ''
635
        );
636
        if (!is_array($menuItems)) {
637
            $menuItems = [];
638
        }
639
        return $menuItems;
640
    }
641
642
    /**
643
     * Fetches all menuitems if special = language is set
644
     *
645
     * @param string $specialValue The value from special.value
646
     * @return array
647
     */
648
    protected function prepareMenuItemsForLanguageMenu($specialValue)
649
    {
650
        $menuItems = [];
651
        // Getting current page record NOT overlaid by any translation:
652
        $tsfe = $this->getTypoScriptFrontendController();
653
        $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->page['uid']);
654
        // Traverse languages set up:
655
        $languageItems = GeneralUtility::intExplode(',', $specialValue);
656
        foreach ($languageItems as $sUid) {
657
            // Find overlay record:
658
            if ($sUid) {
659
                $lRecs = $this->sys_page->getPageOverlay($tsfe->page['uid'], $sUid);
660
            } else {
661
                $lRecs = [];
662
            }
663
            // Checking if the "disabled" state should be set.
664
            if (GeneralUtility::hideIfNotTranslated($tsfe->page['l18n_cfg']) && $sUid &&
665
                empty($lRecs) || GeneralUtility::hideIfDefaultLanguage($tsfe->page['l18n_cfg']) &&
666
                (!$sUid || empty($lRecs)) ||
667
                !$this->conf['special.']['normalWhenNoLanguage'] && $sUid && empty($lRecs)
668
            ) {
669
                $iState = $tsfe->sys_language_uid == $sUid ? 'USERDEF2' : 'USERDEF1';
670
            } else {
671
                $iState = $tsfe->sys_language_uid == $sUid ? 'ACT' : 'NO';
672
            }
673
            if ($this->conf['addQueryString']) {
674
                $getVars = $this->parent_cObj->getQueryArguments(
675
                    $this->conf['addQueryString.'],
676
                    ['L' => $sUid],
677
                    true
678
                );
679
                $this->analyzeCacheHashRequirements($getVars);
680
            } else {
681
                $getVars = '&L=' . $sUid;
682
            }
683
            // Adding menu item:
684
            $menuItems[] = array_merge(
685
                array_merge($currentPageWithNoOverlay, $lRecs),
0 ignored issues
show
Bug introduced by
It seems like $currentPageWithNoOverlay can also be of type integer; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

685
                array_merge(/** @scrutinizer ignore-type */ $currentPageWithNoOverlay, $lRecs),
Loading history...
686
                [
687
                    'ITEM_STATE' => $iState,
688
                    '_ADD_GETVARS' => $getVars,
689
                    '_SAFE' => true
690
                ]
691
            );
692
        }
693
        return $menuItems;
694
    }
695
696
    /**
697
     * Fetches all menuitems if special = directory is set
698
     *
699
     * @param string $specialValue The value from special.value
700
     * @param string $sortingField The sorting field
701
     * @return array
702
     */
703
    protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
704
    {
705
        $tsfe = $this->getTypoScriptFrontendController();
706
        $menuItems = [];
707
        if ($specialValue == '') {
708
            $specialValue = $tsfe->page['uid'];
709
        }
710
        $items = GeneralUtility::intExplode(',', $specialValue);
711
        foreach ($items as $id) {
712
            $MP = $this->tmpl->getFromMPmap($id);
713
            // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
714
            $mount_info = $this->sys_page->getMountPointInfo($id);
715
            if (is_array($mount_info)) {
716
                if ($mount_info['overlay']) {
717
                    // Overlays should already have their full MPvars calculated:
718
                    $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
719
                    $MP = $MP ? $MP : $mount_info['MPvar'];
720
                } else {
721
                    $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
722
                }
723
                $id = $mount_info['mount_pid'];
724
            }
725
            // Get sub-pages:
726
            $statement = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
727
            while ($row = $statement->fetch()) {
728
                $tsfe->sys_page->versionOL('pages', $row, true);
729
                if (!empty($row)) {
730
                    // Keep mount point?
731
                    $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
732
                    // There is a valid mount point.
733
                    if (is_array($mount_info) && $mount_info['overlay']) {
734
                        // Using "getPage" is OK since we need the check for enableFields
735
                        // AND for type 2 of mount pids we DO require a doktype < 200!
736
                        $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
737
                        if (!empty($mp_row)) {
738
                            $row = $mp_row;
739
                            $row['_MP_PARAM'] = $mount_info['MPvar'];
740
                        } else {
741
                            // If the mount point could not be fetched with respect
742
                            // to enableFields, unset the row so it does not become a part of the menu!
743
                            unset($row);
744
                        }
745
                    }
746
                    // Add external MP params, then the row:
747
                    if (!empty($row)) {
748
                        if ($MP) {
749
                            $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
750
                        }
751
                        $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
752
                    }
753
                }
754
            }
755
        }
756
757
        return $menuItems;
758
    }
759
760
    /**
761
     * Fetches all menuitems if special = list is set
762
     *
763
     * @param string $specialValue The value from special.value
764
     * @return array
765
     */
766
    protected function prepareMenuItemsForListMenu($specialValue)
767
    {
768
        $menuItems = [];
769
        if ($specialValue == '') {
770
            $specialValue = $this->id;
771
        }
772
        $skippedEnableFields = [];
773
        if (!empty($this->mconf['showAccessRestrictedPages'])) {
774
            $skippedEnableFields = ['fe_group' => 1];
775
        }
776
        /** @var RelationHandler $loadDB*/
777
        $loadDB = GeneralUtility::makeInstance(RelationHandler::class);
778
        $loadDB->setFetchAllFields(true);
779
        $loadDB->start($specialValue, 'pages');
780
        $loadDB->additionalWhere['pages'] = $this->parent_cObj->enableFields('pages', false, $skippedEnableFields);
781
        $loadDB->getFromDB();
782
        foreach ($loadDB->itemArray as $val) {
783
            $MP = $this->tmpl->getFromMPmap($val['id']);
784
            // Keep mount point?
785
            $mount_info = $this->sys_page->getMountPointInfo($val['id']);
786
            // There is a valid mount point.
787
            if (is_array($mount_info) && $mount_info['overlay']) {
788
                // Using "getPage" is OK since we need the check for enableFields
789
                // AND for type 2 of mount pids we DO require a doktype < 200!
790
                $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
791
                if (!empty($mp_row)) {
792
                    $row = $mp_row;
793
                    $row['_MP_PARAM'] = $mount_info['MPvar'];
794
                    // Overlays should already have their full MPvars calculated
795
                    if ($mount_info['overlay']) {
796
                        $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
797
                        if ($MP) {
798
                            unset($row['_MP_PARAM']);
799
                        }
800
                    }
801
                } else {
802
                    // If the mount point could not be fetched with respect to
803
                    // enableFields, unset the row so it does not become a part of the menu!
804
                    unset($row);
805
                }
806
            } else {
807
                $row = $loadDB->results['pages'][$val['id']];
808
            }
809
            // Add versioning overlay for current page (to respect workspaces)
810
            if (isset($row) && is_array($row)) {
811
                $this->sys_page->versionOL('pages', $row, true);
812
            }
813
            // Add external MP params, then the row:
814
            if (isset($row) && is_array($row)) {
815
                if ($MP) {
816
                    $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
817
                }
818
                $menuItems[] = $this->sys_page->getPageOverlay($row);
819
            }
820
        }
821
        return $menuItems;
822
    }
823
824
    /**
825
     * Fetches all menuitems if special = updated is set
826
     *
827
     * @param string $specialValue The value from special.value
828
     * @param string $sortingField The sorting field
829
     * @return array
830
     */
831
    protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
832
    {
833
        $tsfe = $this->getTypoScriptFrontendController();
834
        $menuItems = [];
835
        if ($specialValue == '') {
836
            $specialValue = $tsfe->page['uid'];
837
        }
838
        $items = GeneralUtility::intExplode(',', $specialValue);
839
        if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
840
            $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
841
        } else {
842
            $depth = 20;
843
        }
844
        // Max number of items
845
        $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
846
        $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
847
        if (!$limit) {
848
            $limit = 10;
849
        }
850
        // *'auto', 'manual', 'tstamp'
851
        $mode = $this->conf['special.']['mode'];
852
        // Get id's
853
        $id_list_arr = [];
854
        foreach ($items as $id) {
855
            $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
856
            $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $bA, $bA - 1);
857
        }
858
        $id_list = implode(',', $id_list_arr);
859
        // Get sortField (mode)
860
        switch ($mode) {
861
            case 'starttime':
862
                $sortField = 'starttime';
863
                break;
864
            case 'lastUpdated':
865
            case 'manual':
866
                $sortField = 'lastUpdated';
867
                break;
868
            case 'tstamp':
869
                $sortField = 'tstamp';
870
                break;
871
            case 'crdate':
872
                $sortField = 'crdate';
873
                break;
874
            default:
875
                $sortField = 'SYS_LASTCHANGED';
876
        }
877
        $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
878
        if ($this->conf['special.']['excludeNoSearchPages']) {
879
            $extraWhere .= ' AND pages.no_search=0';
880
        }
881
        if ($maxAge > 0) {
882
            $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
883
        }
884
        $statement = $this->parent_cObj->exec_getQuery('pages', [
885
            'pidInList' => '0',
886
            'uidInList' => $id_list,
887
            'where' => $sortField . '>=0' . $extraWhere,
888
            'orderBy' => $sortingField ?: $sortField . ' DESC',
889
            'max' => $limit
890
        ]);
891
        while ($row = $statement->fetch()) {
892
            $tsfe->sys_page->versionOL('pages', $row, true);
893
            if (is_array($row)) {
894
                $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
895
            }
896
        }
897
898
        return $menuItems;
899
    }
900
901
    /**
902
     * Fetches all menuitems if special = keywords is set
903
     *
904
     * @param string $specialValue The value from special.value
905
     * @param string $sortingField The sorting field
906
     * @return array
907
     */
908
    protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
909
    {
910
        $tsfe = $this->getTypoScriptFrontendController();
911
        $menuItems = [];
912
        list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
913
        if (!$specialValue) {
914
            $specialValue = $tsfe->page['uid'];
915
        }
916
        if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
917
            $kw = isset($this->conf['special.']['setKeywords.']) ? $this->parent_cObj->stdWrap($this->conf['special.']['setKeywords'], $this->conf['special.']['setKeywords.']) : $this->conf['special.']['setKeywords'];
918
        } else {
919
            // The page record of the 'value'.
920
            $value_rec = $this->sys_page->getPage($specialValue);
0 ignored issues
show
Bug introduced by
It seems like $specialValue can also be of type string; however, parameter $uid of TYPO3\CMS\Frontend\Page\PageRepository::getPage() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

920
            $value_rec = $this->sys_page->getPage(/** @scrutinizer ignore-type */ $specialValue);
Loading history...
921
            $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
922
            // keywords.
923
            $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
924
        }
925
        // *'auto', 'manual', 'tstamp'
926
        $mode = $this->conf['special.']['mode'];
927
        switch ($mode) {
928
            case 'starttime':
929
                $sortField = 'starttime';
930
                break;
931
            case 'lastUpdated':
932
            case 'manual':
933
                $sortField = 'lastUpdated';
934
                break;
935
            case 'tstamp':
936
                $sortField = 'tstamp';
937
                break;
938
            case 'crdate':
939
                $sortField = 'crdate';
940
                break;
941
            default:
942
                $sortField = 'SYS_LASTCHANGED';
943
        }
944
        // Depth, limit, extra where
945
        if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
946
            $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
947
        } else {
948
            $depth = 20;
949
        }
950
        // Max number of items
951
        $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
952
        // Start point
953
        $eLevel = $this->parent_cObj->getKey(
954
            isset($this->conf['special.']['entryLevel.'])
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $this->paren...pecial.']['entryLevel'] can also be of type string; however, parameter $key of TYPO3\CMS\Frontend\Conte...bjectRenderer::getKey() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

954
            /** @scrutinizer ignore-type */ isset($this->conf['special.']['entryLevel.'])
Loading history...
955
            ? $this->parent_cObj->stdWrap($this->conf['special.']['entryLevel'], $this->conf['special.']['entryLevel.'])
956
            : $this->conf['special.']['entryLevel'],
957
            $this->tmpl->rootLine
958
        );
959
        $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
960
        // Which field is for keywords
961
        $kfield = 'keywords';
962
        if ($this->conf['special.']['keywordsField']) {
963
            list($kfield) = explode(' ', trim($this->conf['special.']['keywordsField']));
964
        }
965
        // If there are keywords and the startuid is present
966
        if ($kw && $startUid) {
967
            $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
968
            $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
969
            $kwArr = GeneralUtility::trimExplode(',', $kw, true);
970
            $keyWordsWhereArr = [];
971
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
972
            foreach ($kwArr as $word) {
973
                $keyWordsWhereArr[] = $queryBuilder->expr()->like(
974
                    $kfield,
975
                    $queryBuilder->createNamedParameter(
976
                        '%' . $queryBuilder->escapeLikeWildcards($word) . '%',
977
                        \PDO::PARAM_STR
978
                    )
979
                );
980
            }
981
            $queryBuilder
982
                ->select('*')
983
                ->from('pages')
984
                ->where(
985
                    $queryBuilder->expr()->in(
986
                        'uid',
987
                        GeneralUtility::intExplode(',', $id_list, true)
988
                    ),
989
                    $queryBuilder->expr()->neq(
990
                        'uid',
991
                        $queryBuilder->createNamedParameter($specialValue, \PDO::PARAM_INT)
992
                    )
993
                );
994
995
            if (count($keyWordsWhereArr) !== 0) {
996
                $queryBuilder->andWhere($queryBuilder->expr()->orX(...$keyWordsWhereArr));
997
            }
998
999
            if ($this->doktypeExcludeList) {
1000
                $queryBuilder->andWhere(
1001
                    $queryBuilder->expr()->notIn(
1002
                        'pages.doktype',
1003
                        GeneralUtility::intExplode(',', $this->doktypeExcludeList, true)
1004
                    )
1005
                );
1006
            }
1007
1008
            if (!$this->conf['includeNotInMenu']) {
1009
                $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.nav_hide', 0));
1010
            }
1011
1012
            if ($this->conf['special.']['excludeNoSearchPages']) {
1013
                $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.no_search', 0));
1014
            }
1015
1016
            if ($limit > 0) {
1017
                $queryBuilder->setMaxResults($limit);
1018
            }
1019
1020
            if ($sortingField) {
1021
                $queryBuilder->orderBy($sortingField);
1022
            } else {
1023
                $queryBuilder->orderBy($sortField, 'desc');
1024
            }
1025
1026
            $result = $queryBuilder->execute();
1027
            while ($row = $result->fetch()) {
1028
                $tsfe->sys_page->versionOL('pages', $row, true);
1029
                if (is_array($row)) {
1030
                    $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
1031
                }
1032
            }
1033
        }
1034
1035
        return $menuItems;
1036
    }
1037
1038
    /**
1039
     * Fetches all menuitems if special = rootline is set
1040
     *
1041
     * @return array
1042
     */
1043
    protected function prepareMenuItemsForRootlineMenu()
1044
    {
1045
        $menuItems = [];
1046
        $range = isset($this->conf['special.']['range.'])
1047
            ? $this->parent_cObj->stdWrap($this->conf['special.']['range'], $this->conf['special.']['range.'])
1048
            : $this->conf['special.']['range'];
1049
        $begin_end = explode('|', $range);
1050
        $begin_end[0] = (int)$begin_end[0];
1051
        if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1052
            $begin_end[1] = -1;
1053
        }
1054
        $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1055
        $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1056
        if ($endKey < $beginKey) {
1057
            $endKey = $beginKey;
1058
        }
1059
        $rl_MParray = [];
1060
        foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1061
            // For overlaid mount points, set the variable right now:
1062
            if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1063
                $rl_MParray[] = $v_rl['_MP_PARAM'];
1064
            }
1065
            // Traverse rootline:
1066
            if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1067
                $temp_key = $k_rl;
1068
                $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1069
                if (!empty($menuItems[$temp_key])) {
1070
                    // If there are no specific target for the page, put the level specific target on.
1071
                    if (!$menuItems[$temp_key]['target']) {
1072
                        $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1073
                        $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1074
                    }
1075
                } else {
1076
                    unset($menuItems[$temp_key]);
1077
                }
1078
            }
1079
            // For normal mount points, set the variable for next level.
1080
            if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1081
                $rl_MParray[] = $v_rl['_MP_PARAM'];
1082
            }
1083
        }
1084
        // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1085
        if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1086
            $menuItems = array_reverse($menuItems);
1087
        }
1088
        return $menuItems;
1089
    }
1090
1091
    /**
1092
     * Fetches all menuitems if special = browse is set
1093
     *
1094
     * @param string $specialValue The value from special.value
1095
     * @param string $sortingField The sorting field
1096
     * @param string $additionalWhere Additional WHERE clause
1097
     * @return array
1098
     */
1099
    protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1100
    {
1101
        $menuItems = [];
1102
        list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
1103
        if (!$specialValue) {
1104
            $specialValue = $this->getTypoScriptFrontendController()->page['uid'];
1105
        }
1106
        // Will not work out of rootline
1107
        if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1108
            $recArr = [];
1109
            // The page record of the 'value'.
1110
            $value_rec = $this->sys_page->getPage($specialValue);
0 ignored issues
show
Bug introduced by
It seems like $specialValue can also be of type string; however, parameter $uid of TYPO3\CMS\Frontend\Page\PageRepository::getPage() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1110
            $value_rec = $this->sys_page->getPage(/** @scrutinizer ignore-type */ $specialValue);
Loading history...
1111
            // 'up' page cannot be outside rootline
1112
            if ($value_rec['pid']) {
1113
                // The page record of 'up'.
1114
                $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1115
            }
1116
            // If the 'up' item was NOT level 0 in rootline...
1117
            if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1118
                // The page record of "index".
1119
                $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1120
            }
1121
            // check if certain pages should be excluded
1122
            $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1123
            if ($this->conf['special.']['excludeNoSearchPages']) {
1124
                $additionalWhere .= ' AND pages.no_search=0';
1125
            }
1126
            // prev / next is found
1127
            $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1128
            $lastKey = 0;
1129
            $nextActive = 0;
1130
            foreach ($prevnext_menu as $k_b => $v_b) {
1131
                if ($nextActive) {
1132
                    $recArr['next'] = $v_b;
1133
                    $nextActive = 0;
1134
                }
1135
                if ($v_b['uid'] == $specialValue) {
1136
                    if ($lastKey) {
1137
                        $recArr['prev'] = $prevnext_menu[$lastKey];
1138
                    }
1139
                    $nextActive = 1;
1140
                }
1141
                $lastKey = $k_b;
1142
            }
1143
1144
            $recArr['first'] = reset($prevnext_menu);
1145
            $recArr['last'] = end($prevnext_menu);
1146
            // prevsection / nextsection is found
1147
            // You can only do this, if there is a valid page two levels up!
1148
            if (!empty($recArr['index']['uid'])) {
1149
                $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1150
                $lastKey = 0;
1151
                $nextActive = 0;
1152
                foreach ($prevnextsection_menu as $k_b => $v_b) {
1153
                    if ($nextActive) {
1154
                        $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1155
                        if (!empty($sectionRec_temp)) {
1156
                            $recArr['nextsection'] = reset($sectionRec_temp);
1157
                            $recArr['nextsection_last'] = end($sectionRec_temp);
1158
                            $nextActive = 0;
1159
                        }
1160
                    }
1161
                    if ($v_b['uid'] == $value_rec['pid']) {
1162
                        if ($lastKey) {
1163
                            $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1164
                            if (!empty($sectionRec_temp)) {
1165
                                $recArr['prevsection'] = reset($sectionRec_temp);
1166
                                $recArr['prevsection_last'] = end($sectionRec_temp);
1167
                            }
1168
                        }
1169
                        $nextActive = 1;
1170
                    }
1171
                    $lastKey = $k_b;
1172
                }
1173
            }
1174
            if ($this->conf['special.']['items.']['prevnextToSection']) {
1175
                if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1176
                    $recArr['prev'] = $recArr['prevsection_last'];
1177
                }
1178
                if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1179
                    $recArr['next'] = $recArr['nextsection'];
1180
                }
1181
            }
1182
            $items = explode('|', $this->conf['special.']['items']);
1183
            $c = 0;
1184
            foreach ($items as $k_b => $v_b) {
1185
                $v_b = strtolower(trim($v_b));
1186
                if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1187
                    $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1188
                }
1189
                if (is_array($recArr[$v_b])) {
1190
                    $menuItems[$c] = $recArr[$v_b];
1191
                    if ($this->conf['special.'][$v_b . '.']['target']) {
1192
                        $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1193
                    }
1194
                    $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1195
                    if (is_array($tmpSpecialFields)) {
1196
                        foreach ($tmpSpecialFields as $fk => $val) {
1197
                            $menuItems[$c][$fk] = $val;
1198
                        }
1199
                    }
1200
                    $c++;
1201
                }
1202
            }
1203
        }
1204
        return $menuItems;
1205
    }
1206
1207
    /**
1208
     * Analyzes the parameters to find if the link needs a cHash parameter.
1209
     *
1210
     * @param string $queryString
1211
     */
1212
    protected function analyzeCacheHashRequirements($queryString)
1213
    {
1214
        $parameters = GeneralUtility::explodeUrl2Array($queryString);
1215
        if (!empty($parameters)) {
1216
            if (!isset($parameters['id'])) {
1217
                $queryString .= '&id=' . $this->getTypoScriptFrontendController()->id;
1218
            }
1219
            /** @var CacheHashCalculator $cacheHashCalculator */
1220
            $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
1221
            $cHashParameters = $cacheHashCalculator->getRelevantParameters($queryString);
1222
            if (count($cHashParameters) > 1) {
1223
                $this->useCacheHash = (
1224
                    $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ||
1225
                    !isset($parameters['no_cache']) ||
1226
                    !$parameters['no_cache']
1227
                );
1228
            }
1229
        }
1230
    }
1231
1232
    /**
1233
     * Checks if a page is OK to include in the final menu item array. Pages can be excluded if the doktype is wrong,
1234
     * if they are hidden in navigation, have a uid in the list of banned uids etc.
1235
     *
1236
     * @param array $data Array of menu items
1237
     * @param array $banUidArray Array of page uids which are to be excluded
1238
     * @param bool $spacer If set, then the page is a spacer.
1239
     * @return bool Returns TRUE if the page can be safely included.
1240
     *
1241
     * @throws \UnexpectedValueException
1242
     */
1243
    public function filterMenuPages(&$data, $banUidArray, $spacer)
1244
    {
1245
        $includePage = true;
1246
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] ?? [] as $className) {
1247
            $hookObject = GeneralUtility::makeInstance($className);
1248
            if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1249
                throw new \UnexpectedValueException($className . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1250
            }
1251
            $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $spacer, $this);
1252
        }
1253
        if (!$includePage) {
1254
            return false;
1255
        }
1256
        if ($data['_SAFE']) {
1257
            return true;
1258
        }
1259
1260
        if (
1261
            ($this->mconf['SPC'] || !$spacer) // If the spacer-function is not enabled, spacers will not enter the $menuArr
1262
            && (!$data['nav_hide'] || $this->conf['includeNotInMenu']) // Not hidden in navigation
1263
            && !GeneralUtility::inList($this->doktypeExcludeList, $data['doktype']) // Page may not be 'not_in_menu' or 'Backend User Section'
1264
            && !in_array($data['uid'], $banUidArray, false) // not in banned uid's
1265
        ) {
1266
            // Checks if the default language version can be shown:
1267
            // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
1268
            $tsfe = $this->getTypoScriptFrontendController();
1269
            $blockPage = GeneralUtility::hideIfDefaultLanguage($data['l18n_cfg']) && (!$tsfe->sys_language_uid || $tsfe->sys_language_uid && !$data['_PAGES_OVERLAY']);
1270
            if (!$blockPage) {
1271
                // Checking if a page should be shown in the menu depending on whether a translation exists:
1272
                $tok = true;
1273
                // There is an alternative language active AND the current page requires a translation:
1274
                if ($tsfe->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1275
                    if (!$data['_PAGES_OVERLAY']) {
1276
                        $tok = false;
1277
                    }
1278
                }
1279
                // Continue if token is TRUE:
1280
                if ($tok) {
1281
                    // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1282
                    if ($this->conf['protectLvar']) {
1283
                        $languageUid = (int)$tsfe->config['config']['sys_language_uid'];
1284
                        if ($languageUid && ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
1285
                            $olRec = $tsfe->sys_page->getPageOverlay($data['uid'], $languageUid);
1286
                            if (empty($olRec)) {
1287
                                // If no page translation record then page can NOT be accessed in
1288
                                // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1289
                                $data['_ADD_GETVARS'] .= '&L=0';
1290
                            }
1291
                        }
1292
                    }
1293
                    return true;
1294
                }
1295
            }
1296
        }
1297
        return false;
1298
    }
1299
1300
    /**
1301
     * Generating the per-menu-item configuration arrays based on the settings for item states (NO, RO, ACT, CUR etc)
1302
     * set in ->mconf (config for the current menu object)
1303
     * Basically it will produce an individual array for each menu item based on the item states.
1304
     * BUT in addition the "optionSplit" syntax for the values is ALSO evaluated here so that all property-values
1305
     * are "option-splitted" and the output will thus be resolved.
1306
     * Is called from the "generate" functions in the extension classes. The function is processor intensive due to
1307
     * the option split feature in particular. But since the generate function is not always called
1308
     * (since the ->result array may be cached, see makeMenu) it doesn't hurt so badly.
1309
     *
1310
     * @param int $splitCount Number of menu items in the menu
1311
     * @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)
1312
     *
1313
     * @internal
1314
     */
1315
    public function procesItemStates($splitCount)
1316
    {
1317
        // Prepare normal settings
1318
        if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1319
            // Setting a blank array if NO=1 and there are no properties.
1320
            $this->mconf['NO.'] = [];
1321
        }
1322
        $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1323
        $NOconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['NO.'], $splitCount);
1324
        // Prepare rollOver settings, overriding normal settings
1325
        $ROconf = [];
1326
        if ($this->mconf['RO']) {
1327
            $ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['RO.'], $splitCount);
1328
        }
1329
        // Prepare IFSUB settings, overriding normal settings
1330
        // IFSUB is TRUE if there exist submenu items to the current item
1331
        if (!empty($this->mconf['IFSUB'])) {
1332
            $IFSUBconf = null;
1333
            $IFSUBROconf = null;
1334
            foreach ($NOconf as $key => $val) {
1335
                if ($this->isItemState('IFSUB', $key)) {
1336
                    // if this is the first IFSUB element, we must generate IFSUB.
1337
                    if ($IFSUBconf === null) {
1338
                        $IFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['IFSUB.'], $splitCount);
1339
                        if (!empty($this->mconf['IFSUBRO'])) {
1340
                            $IFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['IFSUBRO.'], $splitCount);
1341
                        }
1342
                    }
1343
                    // Substitute normal with ifsub
1344
                    if (isset($IFSUBconf[$key])) {
1345
                        $NOconf[$key] = $IFSUBconf[$key];
1346
                    }
1347
                    // If rollOver on normal, we must apply a state for rollOver on the active
1348
                    if ($ROconf) {
1349
                        // If RollOver on active then apply this
1350
                        $ROconf[$key] = !empty($IFSUBROconf[$key]) ? $IFSUBROconf[$key] : $IFSUBconf[$key];
1351
                    }
1352
                }
1353
            }
1354
        }
1355
        // Prepare active settings, overriding normal settings
1356
        if (!empty($this->mconf['ACT'])) {
1357
            $ACTconf = null;
1358
            $ACTROconf = null;
1359
            // Find active
1360
            foreach ($NOconf as $key => $val) {
1361
                if ($this->isItemState('ACT', $key)) {
1362
                    // If this is the first 'active', we must generate ACT.
1363
                    if ($ACTconf === null) {
1364
                        $ACTconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACT.'], $splitCount);
1365
                        // Prepare active rollOver settings, overriding normal active settings
1366
                        if (!empty($this->mconf['ACTRO'])) {
1367
                            $ACTROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTRO.'], $splitCount);
1368
                        }
1369
                    }
1370
                    // Substitute normal with active
1371
                    if (isset($ACTconf[$key])) {
1372
                        $NOconf[$key] = $ACTconf[$key];
1373
                    }
1374
                    // If rollOver on normal, we must apply a state for rollOver on the active
1375
                    if ($ROconf) {
1376
                        // If RollOver on active then apply this
1377
                        $ROconf[$key] = !empty($ACTROconf[$key]) ? $ACTROconf[$key] : $ACTconf[$key];
1378
                    }
1379
                }
1380
            }
1381
        }
1382
        // Prepare ACT (active)/IFSUB settings, overriding normal settings
1383
        // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
1384
        if (!empty($this->mconf['ACTIFSUB'])) {
1385
            $ACTIFSUBconf = null;
1386
            $ACTIFSUBROconf = null;
1387
            // Find active
1388
            foreach ($NOconf as $key => $val) {
1389
                if ($this->isItemState('ACTIFSUB', $key)) {
1390
                    // If this is the first 'active', we must generate ACTIFSUB.
1391
                    if ($ACTIFSUBconf === null) {
1392
                        $ACTIFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTIFSUB.'], $splitCount);
1393
                        // Prepare active rollOver settings, overriding normal active settings
1394
                        if (!empty($this->mconf['ACTIFSUBRO'])) {
1395
                            $ACTIFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTIFSUBRO.'], $splitCount);
1396
                        }
1397
                    }
1398
                    // Substitute normal with active
1399
                    if (isset($ACTIFSUBconf[$key])) {
1400
                        $NOconf[$key] = $ACTIFSUBconf[$key];
1401
                    }
1402
                    // If rollOver on normal, we must apply a state for rollOver on the active
1403
                    if ($ROconf) {
1404
                        // If RollOver on active then apply this
1405
                        $ROconf[$key] = !empty($ACTIFSUBROconf[$key]) ? $ACTIFSUBROconf[$key] : $ACTIFSUBconf[$key];
1406
                    }
1407
                }
1408
            }
1409
        }
1410
        // Prepare CUR (current) settings, overriding normal settings
1411
        // CUR is TRUE if the current page equals the item here!
1412
        if (!empty($this->mconf['CUR'])) {
1413
            $CURconf = null;
1414
            $CURROconf = null;
1415
            foreach ($NOconf as $key => $val) {
1416
                if ($this->isItemState('CUR', $key)) {
1417
                    // if this is the first 'current', we must generate CUR. Basically this control is just inherited
1418
                    // from the other implementations as current would only exist one time and that's it
1419
                    // (unless you use special-features of HMENU)
1420
                    if ($CURconf === null) {
1421
                        $CURconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CUR.'], $splitCount);
1422
                        if (!empty($this->mconf['CURRO'])) {
1423
                            $CURROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURRO.'], $splitCount);
1424
                        }
1425
                    }
1426
                    // Substitute normal with current
1427
                    if (isset($CURconf[$key])) {
1428
                        $NOconf[$key] = $CURconf[$key];
1429
                    }
1430
                    // If rollOver on normal, we must apply a state for rollOver on the active
1431
                    if ($ROconf) {
1432
                        // If RollOver on active then apply this
1433
                        $ROconf[$key] = !empty($CURROconf[$key]) ? $CURROconf[$key] : $CURconf[$key];
1434
                    }
1435
                }
1436
            }
1437
        }
1438
        // Prepare CUR (current)/IFSUB settings, overriding normal settings
1439
        // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
1440
        if (!empty($this->mconf['CURIFSUB'])) {
1441
            $CURIFSUBconf = null;
1442
            $CURIFSUBROconf = null;
1443
            foreach ($NOconf as $key => $val) {
1444
                if ($this->isItemState('CURIFSUB', $key)) {
1445
                    // If this is the first 'current', we must generate CURIFSUB.
1446
                    if ($CURIFSUBconf === null) {
1447
                        $CURIFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURIFSUB.'], $splitCount);
1448
                        // Prepare current rollOver settings, overriding normal current settings
1449
                        if (!empty($this->mconf['CURIFSUBRO'])) {
1450
                            $CURIFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURIFSUBRO.'], $splitCount);
1451
                        }
1452
                    }
1453
                    // Substitute normal with active
1454
                    if ($CURIFSUBconf[$key]) {
1455
                        $NOconf[$key] = $CURIFSUBconf[$key];
1456
                    }
1457
                    // If rollOver on normal, we must apply a state for rollOver on the current
1458
                    if ($ROconf) {
1459
                        // If RollOver on current then apply this
1460
                        $ROconf[$key] = !empty($CURIFSUBROconf[$key]) ? $CURIFSUBROconf[$key] : $CURIFSUBconf[$key];
1461
                    }
1462
                }
1463
            }
1464
        }
1465
        // Prepare active settings, overriding normal settings
1466
        if (!empty($this->mconf['USR'])) {
1467
            $USRconf = null;
1468
            $USRROconf = null;
1469
            // Find active
1470
            foreach ($NOconf as $key => $val) {
1471
                if ($this->isItemState('USR', $key)) {
1472
                    // if this is the first active, we must generate USR.
1473
                    if ($USRconf === null) {
1474
                        $USRconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USR.'], $splitCount);
1475
                        // Prepare active rollOver settings, overriding normal active settings
1476
                        if (!empty($this->mconf['USRRO'])) {
1477
                            $USRROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USRRO.'], $splitCount);
1478
                        }
1479
                    }
1480
                    // Substitute normal with active
1481
                    if ($USRconf[$key]) {
1482
                        $NOconf[$key] = $USRconf[$key];
1483
                    }
1484
                    // If rollOver on normal, we must apply a state for rollOver on the active
1485
                    if ($ROconf) {
1486
                        // If RollOver on active then apply this
1487
                        $ROconf[$key] = !empty($USRROconf[$key]) ? $USRROconf[$key] : $USRconf[$key];
1488
                    }
1489
                }
1490
            }
1491
        }
1492
        // Prepare spacer settings, overriding normal settings
1493
        if (!empty($this->mconf['SPC'])) {
1494
            $SPCconf = null;
1495
            // Find spacers
1496
            foreach ($NOconf as $key => $val) {
1497
                if ($this->isItemState('SPC', $key)) {
1498
                    // If this is the first spacer, we must generate SPC.
1499
                    if ($SPCconf === null) {
1500
                        $SPCconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['SPC.'], $splitCount);
1501
                    }
1502
                    // Substitute normal with spacer
1503
                    if (isset($SPCconf[$key])) {
1504
                        $NOconf[$key] = $SPCconf[$key];
1505
                    }
1506
                }
1507
            }
1508
        }
1509
        // Prepare Userdefined settings
1510
        if (!empty($this->mconf['USERDEF1'])) {
1511
            $USERDEF1conf = null;
1512
            $USERDEF1ROconf = null;
1513
            // Find active
1514
            foreach ($NOconf as $key => $val) {
1515
                if ($this->isItemState('USERDEF1', $key)) {
1516
                    // If this is the first active, we must generate USERDEF1.
1517
                    if ($USERDEF1conf === null) {
1518
                        $USERDEF1conf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF1.'], $splitCount);
1519
                        // Prepare active rollOver settings, overriding normal active settings
1520
                        if (!empty($this->mconf['USERDEF1RO'])) {
1521
                            $USERDEF1ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF1RO.'], $splitCount);
1522
                        }
1523
                    }
1524
                    // Substitute normal with active
1525
                    if (isset($USERDEF1conf[$key])) {
1526
                        $NOconf[$key] = $USERDEF1conf[$key];
1527
                    }
1528
                    // If rollOver on normal, we must apply a state for rollOver on the active
1529
                    if ($ROconf) {
1530
                        // If RollOver on active then apply this
1531
                        $ROconf[$key] = !empty($USERDEF1ROconf[$key]) ? $USERDEF1ROconf[$key] : $USERDEF1conf[$key];
1532
                    }
1533
                }
1534
            }
1535
        }
1536
        // Prepare Userdefined settings
1537
        if (!empty($this->mconf['USERDEF2'])) {
1538
            $USERDEF2conf = null;
1539
            $USERDEF2ROconf = null;
1540
            // Find active
1541
            foreach ($NOconf as $key => $val) {
1542
                if ($this->isItemState('USERDEF2', $key)) {
1543
                    // If this is the first active, we must generate USERDEF2.
1544
                    if ($USERDEF2conf === null) {
1545
                        $USERDEF2conf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF2.'], $splitCount);
1546
                        // Prepare active rollOver settings, overriding normal active settings
1547
                        if (!empty($this->mconf['USERDEF2RO'])) {
1548
                            $USERDEF2ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF2RO.'], $splitCount);
1549
                        }
1550
                    }
1551
                    // Substitute normal with active
1552
                    if (isset($USERDEF2conf[$key])) {
1553
                        $NOconf[$key] = $USERDEF2conf[$key];
1554
                    }
1555
                    // If rollOver on normal, we must apply a state for rollOver on the active
1556
                    if ($ROconf) {
1557
                        // If RollOver on active then apply this
1558
                        $ROconf[$key] = !empty($USERDEF2ROconf[$key]) ? $USERDEF2ROconf[$key] : $USERDEF2conf[$key];
1559
                    }
1560
                }
1561
            }
1562
        }
1563
        return [$NOconf, $ROconf];
1564
    }
1565
1566
    /**
1567
     * 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
1568
     * This function doesn't care about the url, because if we let the url be redirected, it will be logged in the stat!!!
1569
     *
1570
     * @param int $key Pointer to a key in the $this->menuArr array where the value for that key represents the menu item we are linking to (page record)
1571
     * @param string $altTarget Alternative target
1572
     * @param string $typeOverride Alternative type
1573
     * @return array Returns an array with A-tag attributes as key/value pairs (HREF, TARGET and onClick)
1574
     * @internal
1575
     */
1576
    public function link($key, $altTarget = '', $typeOverride = '')
1577
    {
1578
        $runtimeCache = $this->getRuntimeCache();
1579
        $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . serialize($this->menuArr[$key]));
1580
        $runtimeCachedLink = $runtimeCache->get($cacheId);
1581
        if ($runtimeCachedLink !== false) {
1582
            return $runtimeCachedLink;
1583
        }
1584
1585
        // Mount points:
1586
        $MP_var = $this->getMPvar($key);
1587
        $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1588
        // Setting override ID
1589
        if ($this->mconf['overrideId'] || $this->menuArr[$key]['overrideId']) {
1590
            $overrideArray = [];
1591
            // If a user script returned the value overrideId in the menu array we use that as page id
1592
            $overrideArray['uid'] = $this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId'];
1593
            $overrideArray['alias'] = '';
1594
            // Clear MP parameters since ID was changed.
1595
            $MP_params = '';
1596
        } else {
1597
            $overrideArray = '';
1598
        }
1599
        // Setting main target:
1600
        if ($altTarget) {
1601
            $mainTarget = $altTarget;
1602
        } elseif ($this->mconf['target.']) {
1603
            $mainTarget = $this->parent_cObj->stdWrap($this->mconf['target'], $this->mconf['target.']);
1604
        } else {
1605
            $mainTarget = $this->mconf['target'];
1606
        }
1607
        // Creating link:
1608
        $addParams = $this->mconf['addParams'] . $MP_params;
1609
        if ($this->mconf['collapse'] && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1610
            $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1611
            $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1612
            $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type boolean expected by parameter $no_cache of TYPO3\CMS\Frontend\Conte...tObject::menuTypoLink(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1612
            $LD = $this->menuTypoLink($thePage, $mainTarget, /** @scrutinizer ignore-type */ '', '', $overrideArray, $addParams, $typeOverride);
Loading history...
1613
        } else {
1614
            $addParams .= $this->I['val']['additionalParams'] . $this->menuArr[$key]['_ADD_GETVARS'];
1615
            $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1616
        }
1617
        // Override default target configuration if option is set
1618
        if ($this->menuArr[$key]['target']) {
1619
            $LD['target'] = $this->menuArr[$key]['target'];
1620
        }
1621
        // Override URL if using "External URL"
1622
        if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_LINK) {
1623
            $externalUrl = $this->getSysPage()->getExtURL($this->menuArr[$key]);
1624
            // Create link using typolink (concerning spamProtectEmailAddresses) for email links
1625
            $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $externalUrl]);
1626
            // Links to emails should not have any target
1627
            if (stripos($externalUrl, 'mailto:') === 0) {
1628
                $LD['target'] = '';
1629
                // use external target for the URL
1630
            } elseif (empty($LD['target']) && !empty($this->getTypoScriptFrontendController()->extTarget)) {
1631
                $LD['target'] = $this->getTypoScriptFrontendController()->extTarget;
1632
            }
1633
        }
1634
1635
        $tsfe = $this->getTypoScriptFrontendController();
1636
1637
        // Override url if current page is a shortcut
1638
        $shortcut = null;
1639
        if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_SHORTCUT && $this->menuArr[$key]['shortcut_mode'] != PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1640
            $menuItem = $this->determineOriginalShortcutPage($this->menuArr[$key]);
1641
            try {
1642
                $shortcut = $tsfe->getPageShortcut(
1643
                    $menuItem['shortcut'],
1644
                    $menuItem['shortcut_mode'],
1645
                    $menuItem['uid'],
1646
                    20,
1647
                    [],
1648
                    true
1649
                );
1650
            } catch (\Exception $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1651
            }
1652
            if (!is_array($shortcut)) {
1653
                $runtimeCache->set($cacheId, []);
1654
                return [];
1655
            }
1656
            // Only setting url, not target
1657
            $LD['totalURL'] = $this->parent_cObj->typoLink_URL([
1658
                'parameter' => $shortcut['uid'],
1659
                'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1660
                'linkAccessRestrictedPages' => !empty($this->mconf['showAccessRestrictedPages'])
1661
            ]);
1662
        }
1663
        if ($shortcut) {
1664
            $pageData = $shortcut;
1665
            $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1666
        } else {
1667
            $pageData = $this->menuArr[$key];
1668
        }
1669
        // Manipulation in case of access restricted pages:
1670
        $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1671
        // Overriding URL / Target if set to do so:
1672
        if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
1673
            $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1674
            if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1675
                $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1676
            }
1677
        }
1678
        // OnClick open in windows.
1679
        $onClick = '';
1680
        if ($this->mconf['JSWindow']) {
1681
            $conf = $this->mconf['JSWindow.'];
1682
            $url = $LD['totalURL'];
1683
            $LD['totalURL'] = '#';
1684
            $onClick = 'openPic('
1685
                . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1686
                . '\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1687
                . GeneralUtility::quoteJSvalue($conf['params']) . '); return false;';
1688
            $tsfe->setJS('openPic');
1689
        }
1690
        // look for type and popup
1691
        // following settings are valid in field target:
1692
        // 230								will add type=230 to the link
1693
        // 230 500x600						will add type=230 to the link and open in popup window with 500x600 pixels
1694
        // 230 _blank						will add type=230 to the link and open with target "_blank"
1695
        // 230x450:resizable=0,location=1	will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1696
        $matches = [];
1697
        $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1698
        if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) || $targetIsType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $targetIsType of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1699
            // has type?
1700
            if ((int)$matches[1] || $targetIsType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $targetIsType of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1701
                $LD['totalURL'] .= (strpos($LD['totalURL'], '?') === false ? '?' : '&') . 'type=' . ($targetIsType ?: (int)$matches[1]);
1702
                $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1703
            }
1704
            // Open in popup window?
1705
            if ($matches[3] && $matches[4]) {
1706
                $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1707
                $onClick = 'vHWin=window.open('
1708
                    . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1709
                    . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1710
                $LD['target'] = '';
1711
            }
1712
        }
1713
        // out:
1714
        $list = [];
1715
        // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1716
        // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1717
        // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1718
        $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1719
        $list['TARGET'] = $LD['target'];
1720
        $list['onClick'] = $onClick;
1721
        $runtimeCache->set($cacheId, $list);
1722
        return $list;
1723
    }
1724
1725
    /**
1726
     * Determines original shortcut destination in page overlays.
1727
     *
1728
     * Since the pages records used for menu rendering are overlaid by default,
1729
     * the original 'shortcut' value is lost, if a translation did not define one.
1730
     *
1731
     * @param array $page
1732
     * @return array
1733
     */
1734
    protected function determineOriginalShortcutPage(array $page)
1735
    {
1736
        // Check if modification is required
1737
        if (
1738
            $this->getTypoScriptFrontendController()->sys_language_uid > 0
1739
            && empty($page['shortcut'])
1740
            && !empty($page['uid'])
1741
            && !empty($page['_PAGES_OVERLAY'])
1742
            && !empty($page['_PAGES_OVERLAY_UID'])
1743
        ) {
1744
            // Using raw record since the record was overlaid and is correct already:
1745
            $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1746
1747
            if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1748
                $page['shortcut'] = $originalPage['shortcut'];
1749
            }
1750
        }
1751
1752
        return $page;
1753
    }
1754
1755
    /**
1756
     * Will change $LD (passed by reference) if the page is access restricted
1757
     *
1758
     * @param array $LD The array from the linkData() function
1759
     * @param array $page Page array
1760
     * @param string $mainTarget Main target value
1761
     * @param string $typeOverride Type number override if any
1762
     */
1763
    public function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1764
    {
1765
        // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1766
        if ($this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1767
            $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1768
            $addParams = str_replace(
1769
                [
1770
                    '###RETURN_URL###',
1771
                    '###PAGE_ID###'
1772
                ],
1773
                [
1774
                    rawurlencode($LD['totalURL']),
1775
                    $page['_SHORTCUT_PAGE_UID'] ?? $page['uid']
1776
                ],
1777
                $this->mconf['showAccessRestrictedPages.']['addParams']
1778
            );
1779
            $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', '', $addParams, $typeOverride);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type boolean expected by parameter $no_cache of TYPO3\CMS\Frontend\Conte...tObject::menuTypoLink(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1779
            $LD = $this->menuTypoLink($thePage, $mainTarget, /** @scrutinizer ignore-type */ '', '', '', $addParams, $typeOverride);
Loading history...
1780
        }
1781
    }
1782
1783
    /**
1784
     * Creates a submenu level to the current level - if configured for.
1785
     *
1786
     * @param int $uid Page id of the current page for which a submenu MAY be produced (if conditions are met)
1787
     * @param string $objSuffix Object prefix, see ->start()
1788
     * @return string HTML content of the submenu
1789
     * @internal
1790
     */
1791
    public function subMenu($uid, $objSuffix = '')
1792
    {
1793
        // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1794
        $altArray = '';
1795
        if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU']) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1796
            $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1797
        }
1798
        // Make submenu if the page is the next active
1799
        $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix];
1800
        // stdWrap for expAll
1801
        if (isset($this->mconf['expAll.'])) {
1802
            $this->mconf['expAll'] = $this->parent_cObj->stdWrap($this->mconf['expAll'], $this->mconf['expAll.']);
1803
        }
1804
        if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !$this->mconf['sectionIndex']) {
1805
            try {
1806
                $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1807
                /** @var $submenu AbstractMenuContentObject */
1808
                $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1809
                $submenu->entryLevel = $this->entryLevel + 1;
1810
                $submenu->rL_uidRegister = $this->rL_uidRegister;
1811
                $submenu->MP_array = $this->MP_array;
1812
                if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1813
                    $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1814
                }
1815
                // Especially scripts that build the submenu needs the parent data
1816
                $submenu->parent_cObj = $this->parent_cObj;
1817
                $submenu->setParentMenu($this->menuArr, $this->I['key']);
1818
                // Setting alternativeMenuTempArray (will be effective only if an array)
1819
                if (is_array($altArray)) {
1820
                    $submenu->alternativeMenuTempArray = $altArray;
0 ignored issues
show
Documentation Bug introduced by
It seems like $altArray of type array is incompatible with the declared type string of property $alternativeMenuTempArray.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1821
                }
1822
                if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1823
                    $submenu->makeMenu();
1824
                    // Memorize the current menu item count
1825
                    $tsfe = $this->getTypoScriptFrontendController();
1826
                    $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1827
                    // Reset the menu item count for the submenu
1828
                    $tsfe->register['count_MENUOBJ'] = 0;
1829
                    $content = $submenu->writeMenu();
1830
                    // Restore the item count now that the submenu has been handled
1831
                    $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1832
                    $tsfe->register['count_menuItems'] = count($this->menuArr);
1833
                    return $content;
1834
                }
1835
            } catch (Exception\NoSuchMenuTypeException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1836
            }
1837
        }
1838
        return '';
1839
    }
1840
1841
    /**
1842
     * Returns TRUE if the page with UID $uid is the NEXT page in root line (which means a submenu should be drawn)
1843
     *
1844
     * @param int $uid Page uid to evaluate.
1845
     * @param string $MPvar MPvar for the current position of item.
1846
     * @return bool TRUE if page with $uid is active
1847
     * @internal
1848
     * @see subMenu()
1849
     */
1850
    public function isNext($uid, $MPvar = '')
1851
    {
1852
        // Check for always active PIDs:
1853
        if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1854
            return true;
1855
        }
1856
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1857
        if ($uid && $testUid == $this->nextActive) {
1858
            return true;
1859
        }
1860
        return false;
1861
    }
1862
1863
    /**
1864
     * Returns TRUE if the page with UID $uid is active (in the current rootline)
1865
     *
1866
     * @param int $uid Page uid to evaluate.
1867
     * @param string $MPvar MPvar for the current position of item.
1868
     * @return bool TRUE if page with $uid is active
1869
     * @internal
1870
     */
1871
    public function isActive($uid, $MPvar = '')
1872
    {
1873
        // Check for always active PIDs:
1874
        if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1875
            return true;
1876
        }
1877
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1878
        if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1879
            return true;
1880
        }
1881
        return false;
1882
    }
1883
1884
    /**
1885
     * Returns TRUE if the page with UID $uid is the CURRENT page (equals $this->getTypoScriptFrontendController()->id)
1886
     *
1887
     * @param int $uid Page uid to evaluate.
1888
     * @param string $MPvar MPvar for the current position of item.
1889
     * @return bool TRUE if page $uid = $this->getTypoScriptFrontendController()->id
1890
     * @internal
1891
     */
1892
    public function isCurrent($uid, $MPvar = '')
1893
    {
1894
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1895
        return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1896
    }
1897
1898
    /**
1899
     * Returns TRUE if there is a submenu with items for the page id, $uid
1900
     * Used by the item states "IFSUB", "ACTIFSUB" and "CURIFSUB" to check if there is a submenu
1901
     *
1902
     * @param int $uid Page uid for which to search for a submenu
1903
     * @return bool Returns TRUE if there was a submenu with items found
1904
     * @internal
1905
     */
1906
    public function isSubMenu($uid)
1907
    {
1908
        $cacheId = 'menucontentobject-is-submenu-decision-' . $uid;
1909
        $runtimeCache = $this->getRuntimeCache();
1910
        $cachedDecision = $runtimeCache->get($cacheId);
1911
        if (isset($cachedDecision['result'])) {
1912
            return $cachedDecision['result'];
1913
        }
1914
        // Looking for a mount-pid for this UID since if that
1915
        // exists we should look for a subpages THERE and not in the input $uid;
1916
        $mount_info = $this->sys_page->getMountPointInfo($uid);
1917
        if (is_array($mount_info)) {
1918
            $uid = $mount_info['mount_pid'];
1919
        }
1920
        $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1921
        $hasSubPages = false;
1922
        $bannedUids = $this->getBannedUids();
1923
        foreach ($recs as $theRec) {
1924
            // no valid subpage if the document type is excluded from the menu
1925
            if (GeneralUtility::inList($this->doktypeExcludeList, $theRec['doktype'])) {
1926
                continue;
1927
            }
1928
            // No valid subpage if the page is hidden inside menus and
1929
            // it wasn't forced to show such entries
1930
            if ($theRec['nav_hide'] && !$this->conf['includeNotInMenu']) {
1931
                continue;
1932
            }
1933
            // No valid subpage if the default language should be shown and the page settings
1934
            // are excluding the visibility of the default language
1935
            if (!$this->getTypoScriptFrontendController()->sys_language_uid && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
1936
                continue;
1937
            }
1938
            // No valid subpage if the alternative language should be shown and the page settings
1939
            // are requiring a valid overlay but it doesn't exists
1940
            $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg']);
1941
            if ($this->getTypoScriptFrontendController()->sys_language_uid && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1942
                continue;
1943
            }
1944
            // No valid subpage if the subpage is banned by excludeUidList
1945
            if (in_array($theRec['uid'], $bannedUids)) {
1946
                continue;
1947
            }
1948
            $hasSubPages = true;
1949
            break;
1950
        }
1951
        $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1952
        return $hasSubPages;
1953
    }
1954
1955
    /**
1956
     * Used by procesItemStates() to evaluate if a menu item (identified by $key) is in a certain state.
1957
     *
1958
     * @param string $kind The item state to evaluate (SPC, IFSUB, ACT etc... but no xxxRO states of course)
1959
     * @param int $key Key pointing to menu item from ->menuArr
1960
     * @return bool Returns TRUE if state matches
1961
     * @internal
1962
     * @see procesItemStates()
1963
     */
1964
    public function isItemState($kind, $key)
1965
    {
1966
        $natVal = false;
1967
        // If any value is set for ITEM_STATE the normal evaluation is discarded
1968
        if ($this->menuArr[$key]['ITEM_STATE']) {
1969
            if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1970
                $natVal = true;
1971
            }
1972
        } else {
1973
            switch ($kind) {
1974
                case 'SPC':
1975
                    $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1976
                    break;
1977
                case 'IFSUB':
1978
                    $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1979
                    break;
1980
                case 'ACT':
1981
                    $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1982
                    break;
1983
                case 'ACTIFSUB':
1984
                    $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1985
                    break;
1986
                case 'CUR':
1987
                    $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
1988
                    break;
1989
                case 'CURIFSUB':
1990
                    $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1991
                    break;
1992
                case 'USR':
1993
                    $natVal = (bool)$this->menuArr[$key]['fe_group'];
1994
                    break;
1995
            }
1996
        }
1997
        return $natVal;
1998
    }
1999
2000
    /**
2001
     * Creates an access-key for a TMENU/GMENU menu item based on the menu item titles first letter
2002
     *
2003
     * @param string $title Menu item title.
2004
     * @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
2005
     * @internal
2006
     */
2007
    public function accessKey($title)
2008
    {
2009
        $tsfe = $this->getTypoScriptFrontendController();
2010
        // The global array ACCESSKEY is used to globally control if letters are already used!!
2011
        $result = [];
2012
        $title = trim(strip_tags($title));
2013
        $titleLen = strlen($title);
2014
        for ($a = 0; $a < $titleLen; $a++) {
2015
            $key = strtoupper(substr($title, $a, 1));
2016
            if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
2017
                $tsfe->accessKey[$key] = 1;
2018
                $result['code'] = ' accesskey="' . $key . '"';
2019
                $result['alt'] = ' (ALT+' . $key . ')';
2020
                $result['key'] = $key;
2021
                break;
2022
            }
2023
        }
2024
        return $result;
2025
    }
2026
2027
    /**
2028
     * Calls a user function for processing of internal data.
2029
     * Used for the properties "IProcFunc" and "itemArrayProcFunc"
2030
     *
2031
     * @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".
2032
     * @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
2033
     * @return mixed The processed $passVar
2034
     * @internal
2035
     */
2036
    public function userProcess($mConfKey, $passVar)
2037
    {
2038
        if ($this->mconf[$mConfKey]) {
2039
            $funcConf = $this->mconf[$mConfKey . '.'];
2040
            $funcConf['parentObj'] = $this;
2041
            $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
2042
        }
2043
        return $passVar;
2044
    }
2045
2046
    /**
2047
     * 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'])
2048
     *
2049
     * @internal
2050
     */
2051
    public function setATagParts()
2052
    {
2053
        $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
2054
        $params = $params !== '' ? ' ' . $params : '';
2055
        $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], true) . $params . '>';
2056
        $this->I['A2'] = '</a>';
2057
    }
2058
2059
    /**
2060
     * Returns the title for the navigation
2061
     *
2062
     * @param string $title The current page title
2063
     * @param string $nav_title The current value of the navigation title
2064
     * @return string Returns the navigation title if it is NOT blank, otherwise the page title.
2065
     * @internal
2066
     */
2067
    public function getPageTitle($title, $nav_title)
2068
    {
2069
        return trim($nav_title) !== '' ? $nav_title : $title;
2070
    }
2071
2072
    /**
2073
     * Return MPvar string for entry $key in ->menuArr
2074
     *
2075
     * @param int $key Pointer to element in ->menuArr
2076
     * @return string MP vars for element.
2077
     * @see link()
2078
     */
2079
    public function getMPvar($key)
2080
    {
2081
        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
2082
            $localMP_array = $this->MP_array;
2083
            // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
2084
            if ($this->menuArr[$key]['_MP_PARAM']) {
2085
                $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
2086
            }
2087
            return !empty($localMP_array) ? implode(',', $localMP_array) : '';
2088
        }
2089
        return '';
2090
    }
2091
2092
    /**
2093
     * Returns where clause part to exclude 'not in menu' pages
2094
     *
2095
     * @return string where clause part.
2096
     * @internal
2097
     */
2098
    public function getDoktypeExcludeWhere()
2099
    {
2100
        return $this->doktypeExcludeList ? ' AND pages.doktype NOT IN (' . $this->doktypeExcludeList . ')' : '';
2101
    }
2102
2103
    /**
2104
     * Returns an array of banned UIDs (from excludeUidList)
2105
     *
2106
     * @return array Array of banned UIDs
2107
     * @internal
2108
     */
2109
    public function getBannedUids()
2110
    {
2111
        $excludeUidList = isset($this->conf['excludeUidList.'])
2112
            ? $this->parent_cObj->stdWrap($this->conf['excludeUidList'], $this->conf['excludeUidList.'])
2113
            : $this->conf['excludeUidList'];
2114
2115
        if (!trim($excludeUidList)) {
2116
            return [];
2117
        }
2118
2119
        $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page['uid'], $excludeUidList);
2120
        return GeneralUtility::intExplode(',', $banUidList);
2121
    }
2122
2123
    /**
2124
     * Calls typolink to create menu item links.
2125
     *
2126
     * @param array $page Page record (uid points where to link to)
2127
     * @param string $oTarget Target frame/window
2128
     * @param bool $no_cache TRUE if caching should be disabled
2129
     * @param string $script Alternative script name (unused)
2130
     * @param array|string $overrideArray Array to override values in $page, empty string to skip override
2131
     * @param string $addParams Parameters to add to URL
2132
     * @param int|string $typeOverride "type" value, empty string means "not set"
2133
     * @return array See linkData
2134
     */
2135
    public function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '')
2136
    {
2137
        $conf = [
2138
            'parameter' => is_array($overrideArray) && $overrideArray['uid'] ? $overrideArray['uid'] : $page['uid']
2139
        ];
2140
        if (MathUtility::canBeInterpretedAsInteger($typeOverride)) {
2141
            $conf['parameter'] .= ',' . (int)$typeOverride;
2142
        }
2143
        if ($addParams) {
2144
            $conf['additionalParams'] = $addParams;
2145
        }
2146
        if ($no_cache) {
2147
            $conf['no_cache'] = true;
2148
        } elseif ($this->useCacheHash) {
2149
            $conf['useCacheHash'] = true;
2150
        }
2151
        if ($oTarget) {
2152
            $conf['target'] = $oTarget;
2153
        }
2154
        if ($page['sectionIndex_uid']) {
2155
            $conf['section'] = $page['sectionIndex_uid'];
2156
        }
2157
        $conf['linkAccessRestrictedPages'] = !empty($this->mconf['showAccessRestrictedPages']);
2158
        $this->parent_cObj->typoLink('|', $conf);
2159
        $LD = $this->parent_cObj->lastTypoLinkLD;
2160
        $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
2161
        return $LD;
2162
    }
2163
2164
    /**
2165
     * Generates a list of content objects with sectionIndex enabled
2166
     * available on a specific page
2167
     *
2168
     * Used for menus with sectionIndex enabled
2169
     *
2170
     * @param string $altSortField Alternative sorting field
2171
     * @param int $pid The page id to search for sections
2172
     * @throws \UnexpectedValueException if the query to fetch the content elements unexpectedly fails
2173
     * @return array
2174
     */
2175
    protected function sectionIndex($altSortField, $pid = null)
2176
    {
2177
        $pid = (int)($pid ?: $this->id);
2178
        $basePageRow = $this->sys_page->getPage($pid);
2179
        if (!is_array($basePageRow)) {
2180
            return [];
2181
        }
2182
        $tsfe = $this->getTypoScriptFrontendController();
2183
        $configuration = $this->mconf['sectionIndex.'];
2184
        $useColPos = 0;
2185
        if (trim($configuration['useColPos']) !== '' || is_array($configuration['useColPos.'])) {
2186
            $useColPos = $tsfe->cObj->stdWrap($configuration['useColPos'], $configuration['useColPos.']);
2187
            $useColPos = (int)$useColPos;
2188
        }
2189
        $selectSetup = [
2190
            'pidInList' => $pid,
2191
            'orderBy' => $altSortField,
2192
            'languageField' => 'sys_language_uid',
2193
            'where' => ''
2194
        ];
2195
2196
        if ($useColPos >= 0) {
2197
            $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2198
                ->getConnectionForTable('tt_content')
2199
                ->getExpressionBuilder();
2200
            $selectSetup['where'] = $expressionBuilder->eq('colPos', $useColPos);
2201
        }
2202
2203
        if ($basePageRow['content_from_pid']) {
2204
            // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
2205
            // the referenced page
2206
            $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
2207
        }
2208
        $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
2209
        if (!$statement) {
2210
            $message = 'SectionIndex: Query to fetch the content elements failed!';
2211
            throw new \UnexpectedValueException($message, 1337334849);
2212
        }
2213
        $result = [];
2214
        while ($row = $statement->fetch()) {
2215
            $this->sys_page->versionOL('tt_content', $row);
2216
            if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
2217
                $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL);
2218
            }
2219
            if ($this->mconf['sectionIndex.']['type'] !== 'all') {
2220
                $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
2221
                $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
2222
                $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
2223
                if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
2224
                    continue;
2225
                }
2226
            }
2227
            if (is_array($row)) {
2228
                $uid = $row['uid'];
2229
                $result[$uid] = $basePageRow;
2230
                $result[$uid]['title'] = $row['header'];
2231
                $result[$uid]['nav_title'] = $row['header'];
2232
                // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
2233
                $result[$uid]['nav_hide'] = 0;
2234
                $result[$uid]['subtitle'] = $row['subheader'];
2235
                $result[$uid]['starttime'] = $row['starttime'];
2236
                $result[$uid]['endtime'] = $row['endtime'];
2237
                $result[$uid]['fe_group'] = $row['fe_group'];
2238
                $result[$uid]['media'] = $row['media'];
2239
                $result[$uid]['header_layout'] = $row['header_layout'];
2240
                $result[$uid]['bodytext'] = $row['bodytext'];
2241
                $result[$uid]['image'] = $row['image'];
2242
                $result[$uid]['sectionIndex_uid'] = $uid;
2243
            }
2244
        }
2245
2246
        return $result;
2247
    }
2248
2249
    /**
2250
     * Returns the sys_page object
2251
     *
2252
     * @return \TYPO3\CMS\Frontend\Page\PageRepository
2253
     */
2254
    public function getSysPage()
2255
    {
2256
        return $this->sys_page;
2257
    }
2258
2259
    /**
2260
     * Returns the parent content object
2261
     *
2262
     * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
2263
     */
2264
    public function getParentContentObject()
2265
    {
2266
        return $this->parent_cObj;
2267
    }
2268
2269
    /**
2270
     * @return TypoScriptFrontendController
2271
     */
2272
    protected function getTypoScriptFrontendController()
2273
    {
2274
        return $GLOBALS['TSFE'];
2275
    }
2276
2277
    /**
2278
     * @return TimeTracker
2279
     */
2280
    protected function getTimeTracker()
2281
    {
2282
        return GeneralUtility::makeInstance(TimeTracker::class);
2283
    }
2284
2285
    /**
2286
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
2287
     */
2288
    protected function getCache()
2289
    {
2290
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2291
    }
2292
2293
    /**
2294
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
2295
     */
2296
    protected function getRuntimeCache()
2297
    {
2298
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
2299
    }
2300
2301
    /**
2302
     * Set the parentMenuArr and key to provide the parentMenu informations to the
2303
     * subMenu, special fur IProcFunc and itemArrayProcFunc user functions.
2304
     *
2305
     * @param array $menuArr
2306
     * @param int $menuItemKey
2307
     * @internal
2308
     */
2309
    public function setParentMenu(array $menuArr = [], $menuItemKey)
2310
    {
2311
        // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
2312
        if (is_array($menuArr)
2313
            && (is_int($menuItemKey) && $menuItemKey >= 0 && isset($menuArr[$menuItemKey]))
2314
        ) {
2315
            $this->parentMenuArr = $menuArr;
0 ignored issues
show
Bug Best Practice introduced by
The property parentMenuArr does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2316
            $this->parentMenuArrItemKey = $menuItemKey;
2317
        }
2318
    }
2319
2320
    /**
2321
     * Check if there is an valid parentMenuArr.
2322
     *
2323
     * @return bool
2324
     */
2325
    protected function hasParentMenuArr()
2326
    {
2327
        return
2328
            $this->menuNumber > 1
2329
            && is_array($this->parentMenuArr)
2330
            && !empty($this->parentMenuArr)
2331
        ;
2332
    }
2333
2334
    /**
2335
     * Check if we have an parentMenutArrItemKey
2336
     */
2337
    protected function hasParentMenuItemKey()
2338
    {
2339
        return null !== $this->parentMenuArrItemKey;
2340
    }
2341
2342
    /**
2343
     * Check if the the parentMenuItem exists
2344
     */
2345
    protected function hasParentMenuItem()
2346
    {
2347
        return
2348
            $this->hasParentMenuArr()
2349
            && $this->hasParentMenuItemKey()
2350
            && isset($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2351
        ;
2352
    }
2353
2354
    /**
2355
     * Get the parentMenuArr, if this is subMenu.
2356
     *
2357
     * @return array
2358
     */
2359
    public function getParentMenuArr()
2360
    {
2361
        return $this->hasParentMenuArr() ? $this->parentMenuArr : [];
2362
    }
2363
2364
    /**
2365
     * Get the parentMenuItem from the parentMenuArr, if this is a subMenu
2366
     *
2367
     * @return array|null
2368
     */
2369
    public function getParentMenuItem()
2370
    {
2371
        // check if we have an parentMenuItem and if it is an array
2372
        if ($this->hasParentMenuItem()
2373
            && is_array($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2374
        ) {
2375
            return $this->getParentMenuArr()[$this->parentMenuArrItemKey];
2376
        }
2377
2378
        return null;
2379
    }
2380
}
2381