Passed
Push — master ( 6f0002...d39f54 )
by
unknown
19:15
created

AbstractMenuContentObject::subMenu()   C

Complexity

Conditions 12
Paths 98

Size

Total Lines 46
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 46
rs 6.9666
c 0
b 0
f 0
cc 12
nc 98
nop 2

How to fix   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
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
17
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Core\Cache\CacheManager;
20
use TYPO3\CMS\Core\Context\Context;
21
use TYPO3\CMS\Core\Context\LanguageAspect;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Database\RelationHandler;
24
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
25
use TYPO3\CMS\Core\Page\AssetCollector;
26
use TYPO3\CMS\Core\Site\Entity\Site;
27
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
28
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
29
use TYPO3\CMS\Core\TypoScript\TemplateService;
30
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Core\Utility\MathUtility;
33
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
34
use TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException;
35
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
36
use TYPO3\CMS\Frontend\Typolink\PageLinkBuilder;
37
38
/**
39
 * Generating navigation/menus from TypoScript
40
 *
41
 * The HMENU content object uses this (or more precisely one of the extension classes).
42
 * Among others the class generates an array of menu items. Thereafter functions from the subclasses are called.
43
 * The class is always used through extension classes like TextMenuContentObject.
44
 */
45
abstract class AbstractMenuContentObject
46
{
47
    /**
48
     * tells you which menu number this is. This is important when getting data from the setup
49
     *
50
     * @var int
51
     */
52
    protected $menuNumber = 1;
53
54
    /**
55
     * 0 = rootFolder
56
     *
57
     * @var int
58
     */
59
    protected $entryLevel = 0;
60
61
    /**
62
     * Doktypes that define which should not be included in a menu
63
     *
64
     * @var int[]
65
     */
66
    protected $excludedDoktypes = [PageRepository::DOKTYPE_BE_USER_SECTION, PageRepository::DOKTYPE_SYSFOLDER];
67
68
    /**
69
     * @var int[]
70
     */
71
    protected $alwaysActivePIDlist = [];
72
73
    /**
74
     * Loaded with the parent cObj-object when a new HMENU is made
75
     *
76
     * @var ContentObjectRenderer
77
     */
78
    public $parent_cObj;
79
80
    /**
81
     * accumulation of mount point data
82
     *
83
     * @var string[]
84
     */
85
    protected $MP_array = [];
86
87
    /**
88
     * HMENU configuration
89
     *
90
     * @var array
91
     */
92
    protected $conf = [];
93
94
    /**
95
     * xMENU configuration (TMENU etc)
96
     *
97
     * @var array
98
     */
99
    protected $mconf = [];
100
101
    /**
102
     * @var TemplateService
103
     */
104
    protected $tmpl;
105
106
    /**
107
     * @var PageRepository
108
     */
109
    protected $sys_page;
110
111
    /**
112
     * The base page-id of the menu.
113
     *
114
     * @var int
115
     */
116
    protected $id;
117
118
    /**
119
     * Holds the page uid of the NEXT page in the root line from the page pointed to by entryLevel;
120
     * Used to expand the menu automatically if in a certain root line.
121
     *
122
     * @var string
123
     */
124
    protected $nextActive;
125
126
    /**
127
     * The array of menuItems which is built
128
     *
129
     * @var array[]
130
     */
131
    protected $menuArr;
132
133
    /**
134
     * @var string
135
     */
136
    protected $hash;
137
138
    /**
139
     * @var array
140
     */
141
    protected $result = [];
142
143
    /**
144
     * Is filled with an array of page uid numbers + RL parameters which are in the current
145
     * root line (used to evaluate whether a menu item is in active state)
146
     *
147
     * @var array
148
     */
149
    protected $rL_uidRegister;
150
151
    /**
152
     * @var mixed[]
153
     */
154
    protected $I;
155
156
    /**
157
     * @var string
158
     */
159
    protected $WMresult;
160
161
    /**
162
     * @var int
163
     */
164
    protected $WMmenuItems;
165
166
    /**
167
     * @var array[]
168
     */
169
    protected $WMsubmenuObjSuffixes;
170
171
    /**
172
     * @var ContentObjectRenderer
173
     */
174
    protected $WMcObj;
175
176
    protected ?ServerRequestInterface $request = null;
177
178
    /**
179
     * Can be set to contain menu item arrays for sub-levels.
180
     *
181
     * @var array
182
     */
183
    protected $alternativeMenuTempArray = [];
184
185
    /**
186
     * Array key of the parentMenuItem in the parentMenuArr, if this menu is a subMenu.
187
     *
188
     * @var int|null
189
     */
190
    protected $parentMenuArrItemKey;
191
192
    /**
193
     * @var array
194
     */
195
    protected $parentMenuArr;
196
197
    protected const customItemStates = [
198
        // IFSUB is TRUE if there exist submenu items to the current item
199
        'IFSUB',
200
        'ACT',
201
        // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
202
        'ACTIFSUB',
203
        // CUR is TRUE if the current page equals the item here!
204
        'CUR',
205
        // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
206
        'CURIFSUB',
207
        'USR',
208
        'SPC',
209
        'USERDEF1',
210
        'USERDEF2'
211
    ];
212
213
    /**
214
     * The initialization of the object. This just sets some internal variables.
215
     *
216
     * @param TemplateService $tmpl The $this->getTypoScriptFrontendController()->tmpl object
217
     * @param PageRepository $sys_page The $this->getTypoScriptFrontendController()->sys_page object
218
     * @param int|string $id A starting point page id. This should probably be blank since the 'entryLevel' value will be used then.
219
     * @param array $conf The TypoScript configuration for the HMENU cObject
220
     * @param int $menuNumber Menu number; 1,2,3. Should probably be 1
221
     * @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")
222
     * @param ServerRequestInterface|null $request
223
     * @return bool Returns TRUE on success
224
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::HMENU()
225
     */
226
    public function start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix = '', ?ServerRequestInterface $request = null)
227
    {
228
        $tsfe = $this->getTypoScriptFrontendController();
229
        $this->conf = $conf;
230
        $this->menuNumber = $menuNumber;
231
        $this->mconf = $conf[$this->menuNumber . $objSuffix . '.'];
232
        $this->request = $request;
233
        $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
234
        // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the PageRepository object
235
        if ($this->conf[$this->menuNumber . $objSuffix] && is_object($tmpl) && is_object($sys_page)) {
236
            $this->tmpl = $tmpl;
237
            $this->sys_page = $sys_page;
238
            // alwaysActivePIDlist initialized:
239
            $this->conf['alwaysActivePIDlist'] = (string)$this->parent_cObj->stdWrapValue('alwaysActivePIDlist', $this->conf ?? []);
240
            if (trim($this->conf['alwaysActivePIDlist'])) {
241
                $this->alwaysActivePIDlist = GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
242
            }
243
            // includeNotInMenu initialized:
244
            $this->conf['includeNotInMenu'] = $this->parent_cObj->stdWrapValue('includeNotInMenu', $this->conf, false);
245
            // exclude doktypes that should not be shown in menu (e.g. backend user section)
246
            if ($this->conf['excludeDoktypes'] ?? false) {
247
                $this->excludedDoktypes = GeneralUtility::intExplode(',', $this->conf['excludeDoktypes']);
248
            }
249
            // EntryLevel
250
            $this->entryLevel = $this->parent_cObj->getKey(
251
                $this->parent_cObj->stdWrapValue('entryLevel', $this->conf ?? []),
0 ignored issues
show
Bug introduced by
It seems like $this->parent_cObj->stdW...$this->conf ?? array()) can also be of type boolean and 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

251
                /** @scrutinizer ignore-type */ $this->parent_cObj->stdWrapValue('entryLevel', $this->conf ?? []),
Loading history...
252
                $this->tmpl->rootLine
253
            );
254
            // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
255
            // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
256
            if ($id) {
257
                $this->id = (int)$id;
258
            } else {
259
                // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
260
                $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
261
                // Traverse rootline to build MP_array of pages BEFORE the entryLevel
262
                // (MP var for ->id is picked up in the next part of the code...)
263
                foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
264
                    // For overlaid mount points, set the variable right now:
265
                    if (($levelRec['_MP_PARAM'] ?? false) && ($levelRec['_MOUNT_OL'] ?? false)) {
266
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
267
                    }
268
                    // Break when entry level is reached:
269
                    if ($entryLevel >= $this->entryLevel) {
270
                        break;
271
                    }
272
                    // For normal mount points, set the variable for next level.
273
                    if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
274
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
275
                    }
276
                }
277
            }
278
            // Return FALSE if no page ID was set (thus no menu of subpages can be made).
279
            if ($this->id <= 0) {
280
                return false;
281
            }
282
            // Check if page is a mount point, and if so set id and MP_array
283
            // (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...)
284
            $mount_info = $this->sys_page->getMountPointInfo($this->id);
285
            if (is_array($mount_info)) {
286
                $this->MP_array[] = $mount_info['MPvar'];
287
                $this->id = $mount_info['mount_pid'];
288
            }
289
            // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
290
            // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
291
            if ($this->rL_uidRegister === null) {
292
                $this->rL_uidRegister = [];
293
                $rl_MParray = [];
294
                foreach ($this->tmpl->rootLine as $v_rl) {
295
                    // For overlaid mount points, set the variable right now:
296
                    if (($v_rl['_MP_PARAM'] ?? false) && ($v_rl['_MOUNT_OL'] ?? false)) {
297
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
298
                    }
299
                    // Add to register:
300
                    $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
301
                        (
302
                            !empty($rl_MParray)
303
                            ? ':' . implode(',', $rl_MParray)
304
                            : ''
305
                        );
306
                    // For normal mount points, set the variable for next level.
307
                    if (($v_rl['_MP_PARAM'] ?? false) && !($v_rl['_MOUNT_OL'] ?? false)) {
308
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
309
                    }
310
                }
311
            }
312
            // Set $directoryLevel so the following evaluation of the nextActive will not return
313
            // an invalid value if .special=directory was set
314
            $directoryLevel = 0;
315
            if ($this->conf['special'] === 'directory') {
316
                $value = $this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
317
                if ($value === '') {
318
                    $value = $tsfe->page['uid'];
319
                }
320
                $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
321
            }
322
            // 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
323
            // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
324
            $startLevel = $directoryLevel ?: $this->entryLevel;
325
            $currentLevel = $startLevel + $this->menuNumber;
326
            if (is_array($this->tmpl->rootLine[$currentLevel] ?? null)) {
327
                $nextMParray = $this->MP_array;
328
                if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
329
                    // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
330
                    // otherwise automatic expansion will not work
331
                    $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
332
                    if (isset($parentRecord['_MP_PARAM'])) {
333
                        $nextMParray[] = $parentRecord['_MP_PARAM'];
334
                    }
335
                }
336
                // In overlay mode, add next level MPvars as well:
337
                if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
338
                    $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
339
                }
340
                $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
341
                    (
342
                        !empty($nextMParray)
343
                        ? ':' . implode(',', $nextMParray)
344
                        : ''
345
                    );
346
            } else {
347
                $this->nextActive = '';
348
            }
349
            return true;
350
        }
351
        $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
352
        return false;
353
    }
354
355
    /**
356
     * Creates the menu in the internal variables, ready for output.
357
     * Basically this will read the page records needed and fill in the internal $this->menuArr
358
     * Based on a hash of this array and some other variables the $this->result variable will be
359
     * loaded either from cache OR by calling the generate() method of the class to create the menu for real.
360
     */
361
    public function makeMenu()
362
    {
363
        if (!$this->id) {
364
            return;
365
        }
366
367
        // Initializing showAccessRestrictedPages
368
        $SAVED_where_groupAccess = '';
369
        if ($this->mconf['showAccessRestrictedPages'] ?? false) {
370
            // SAVING where_groupAccess
371
            $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
372
            // Temporarily removing fe_group checking!
373
            $this->sys_page->where_groupAccess = '';
374
        }
375
376
        $menuItems = $this->prepareMenuItems();
377
378
        $c = 0;
379
        $c_b = 0;
380
381
        $minItems = (int)(($this->mconf['minItems'] ?? 0) ?: ($this->conf['minItems'] ?? 0));
382
        $maxItems = (int)(($this->mconf['maxItems'] ?? 0) ?: ($this->conf['maxItems'] ?? 0));
383
        $begin = $this->parent_cObj->calc(($this->mconf['begin'] ?? 0) ?: ($this->conf['begin'] ?? 0));
384
        $minItemsConf = $this->mconf['minItems.'] ?? $this->conf['minItems.'] ?? null;
385
        $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
386
        $maxItemsConf = $this->mconf['maxItems.'] ?? $this->conf['maxItems.'] ?? null;
387
        $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
388
        $beginConf = $this->mconf['begin.'] ?? $this->conf['begin.'] ?? null;
389
        $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
390
        $banUidArray = $this->getBannedUids();
391
        // Fill in the menuArr with elements that should go into the menu:
392
        $this->menuArr = [];
393
        foreach ($menuItems as $data) {
394
            $isSpacerPage = (int)$data['doktype'] === PageRepository::DOKTYPE_SPACER || $data['ITEM_STATE'] === 'SPC';
395
            // if item is a spacer, $spacer is set
396
            if ($this->filterMenuPages($data, $banUidArray, $isSpacerPage)) {
397
                $c_b++;
398
                // If the beginning item has been reached.
399
                if ($begin <= $c_b) {
400
                    $this->menuArr[$c] = $this->determineOriginalShortcutPage($data);
401
                    $this->menuArr[$c]['isSpacer'] = $isSpacerPage;
402
                    $c++;
403
                    if ($maxItems && $c >= $maxItems) {
404
                        break;
405
                    }
406
                }
407
            }
408
        }
409
        // Fill in fake items, if min-items is set.
410
        if ($minItems) {
411
            while ($c < $minItems) {
412
                $this->menuArr[$c] = [
413
                    'title' => '...',
414
                    'uid' => $this->getTypoScriptFrontendController()->id
415
                ];
416
                $c++;
417
            }
418
        }
419
        //	Passing the menuArr through a user defined function:
420
        if ($this->mconf['itemArrayProcFunc'] ?? false) {
421
            $this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
422
        }
423
        // Setting number of menu items
424
        $this->getTypoScriptFrontendController()->register['count_menuItems'] = count($this->menuArr);
425
        $this->hash = md5(
426
            json_encode($this->menuArr) .
427
            json_encode($this->mconf) .
428
            json_encode($this->tmpl->rootLine) .
429
            json_encode($this->MP_array)
430
        );
431
        // Get the cache timeout:
432
        if ($this->conf['cache_period'] ?? false) {
433
            $cacheTimeout = $this->conf['cache_period'];
434
        } else {
435
            $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
436
        }
437
        $cache = $this->getCache();
438
        $cachedData = $cache->get($this->hash);
439
        if (!is_array($cachedData)) {
440
            $this->generate();
441
            $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
442
        } else {
443
            $this->result = $cachedData;
444
        }
445
        // End showAccessRestrictedPages
446
        if ($this->mconf['showAccessRestrictedPages'] ?? false) {
447
            // RESTORING where_groupAccess
448
            $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
449
        }
450
    }
451
452
    /**
453
     * Generates the the menu data.
454
     *
455
     * Subclasses should overwrite this method.
456
     */
457
    public function generate()
458
    {
459
    }
460
461
    /**
462
     * @return string The HTML for the menu
463
     */
464
    public function writeMenu()
465
    {
466
        return '';
467
    }
468
469
    /**
470
     * Gets an array of page rows and removes all, which are not accessible
471
     *
472
     * @param array $pages
473
     * @return array
474
     */
475
    protected function removeInaccessiblePages(array $pages)
476
    {
477
        $banned = $this->getBannedUids();
478
        $filteredPages = [];
479
        foreach ($pages as $aPage) {
480
            if ($this->filterMenuPages($aPage, $banned, (int)$aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
481
                $filteredPages[$aPage['uid']] = $aPage;
482
            }
483
        }
484
        return $filteredPages;
485
    }
486
487
    /**
488
     * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
489
     *
490
     * @return array
491
     */
492
    protected function prepareMenuItems()
493
    {
494
        $menuItems = [];
495
        $alternativeSortingField = trim($this->mconf['alternativeSortingField'] ?? '') ?: 'sorting';
496
497
        // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
498
        $additionalWhere = $this->parent_cObj->stdWrapValue('additionalWhere', $this->mconf ?? []);
499
        $additionalWhere .= $this->getDoktypeExcludeWhere();
500
501
        // ... only for the FIRST level of a HMENU
502
        if ($this->menuNumber == 1 && $this->conf['special']) {
503
            $value = (string)$this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
504
            switch ($this->conf['special']) {
505
                case 'userfunction':
506
                    $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
507
                    break;
508
                case 'language':
509
                    $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
510
                    break;
511
                case 'directory':
512
                    $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
513
                    break;
514
                case 'list':
515
                    $menuItems = $this->prepareMenuItemsForListMenu($value);
516
                    break;
517
                case 'updated':
518
                    $menuItems = $this->prepareMenuItemsForUpdatedMenu(
519
                        $value,
520
                        $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

520
                        /** @scrutinizer ignore-type */ $this->mconf['alternativeSortingField'] ?: false
Loading history...
521
                    );
522
                    break;
523
                case 'keywords':
524
                    $menuItems = $this->prepareMenuItemsForKeywordsMenu(
525
                        $value,
526
                        $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

526
                        /** @scrutinizer ignore-type */ $this->mconf['alternativeSortingField'] ?: false
Loading history...
527
                    );
528
                    break;
529
                case 'categories':
530
                    /** @var CategoryMenuUtility $categoryMenuUtility */
531
                    $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
532
                    $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
533
                    break;
534
                case 'rootline':
535
                    $menuItems = $this->prepareMenuItemsForRootlineMenu();
536
                    break;
537
                case 'browse':
538
                    $menuItems = $this->prepareMenuItemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
539
                    break;
540
            }
541
            if ($this->mconf['sectionIndex'] ?? false) {
542
                $sectionIndexes = [];
543
                foreach ($menuItems as $page) {
544
                    $sectionIndexes = $sectionIndexes + $this->sectionIndex($alternativeSortingField, $page['uid']);
545
                }
546
                $menuItems = $sectionIndexes;
547
            }
548
        } elseif ($this->alternativeMenuTempArray !== []) {
549
            // Setting $menuItems array if not level 1.
550
            $menuItems = $this->alternativeMenuTempArray;
551
        } elseif ($this->mconf['sectionIndex']) {
552
            $menuItems = $this->sectionIndex($alternativeSortingField);
553
        } else {
554
            // Default: Gets a hierarchical menu based on subpages of $this->id
555
            $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
556
        }
557
        return $menuItems;
558
    }
559
560
    /**
561
     * Fetches all menuitems if special = userfunction is set
562
     *
563
     * @param string $specialValue The value from special.value
564
     * @param string $sortingField The sorting field
565
     * @return array
566
     */
567
    protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
568
    {
569
        $menuItems = $this->parent_cObj->callUserFunction(
570
            $this->conf['special.']['userFunc'],
571
            array_merge($this->conf['special.'], ['value' => $specialValue, '_altSortField' => $sortingField]),
572
            ''
573
        );
574
        return is_array($menuItems) ? $menuItems : [];
575
    }
576
577
    /**
578
     * Fetches all menuitems if special = language is set
579
     *
580
     * @param string $specialValue The value from special.value
581
     * @return array
582
     */
583
    protected function prepareMenuItemsForLanguageMenu($specialValue)
584
    {
585
        $menuItems = [];
586
        // Getting current page record NOT overlaid by any translation:
587
        $tsfe = $this->getTypoScriptFrontendController();
588
        $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->page['uid']);
589
590
        if ($specialValue === 'auto') {
591
            $site = $this->getCurrentSite();
592
            $languages = $site->getLanguages();
593
            $languageItems = array_keys($languages);
594
        } else {
595
            $languageItems = GeneralUtility::intExplode(',', $specialValue);
596
        }
597
598
        $tsfe->register['languages_HMENU'] = implode(',', $languageItems);
599
600
        $currentLanguageId = $this->getCurrentLanguageAspect()->getId();
601
602
        foreach ($languageItems as $sUid) {
603
            // Find overlay record:
604
            if ($sUid) {
605
                $lRecs = $this->sys_page->getPageOverlay($tsfe->page['uid'], $sUid);
606
            } else {
607
                $lRecs = [];
608
            }
609
            // Checking if the "disabled" state should be set.
610
            $pageTranslationVisibility = new PageTranslationVisibility((int)($tsfe->page['l18n_cfg'] ?? 0));
611
            if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() && $sUid &&
612
                empty($lRecs) || $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() &&
613
                (!$sUid || empty($lRecs)) ||
614
                !($this->conf['special.']['normalWhenNoLanguage'] ?? false) && $sUid && empty($lRecs)
615
            ) {
616
                $iState = $currentLanguageId === $sUid ? 'USERDEF2' : 'USERDEF1';
617
            } else {
618
                $iState = $currentLanguageId === $sUid ? 'ACT' : 'NO';
619
            }
620
            $getVars = '';
621
            if ($this->conf['addQueryString']) {
622
                $getVars = $this->parent_cObj->getQueryArguments($this->conf['addQueryString.']);
623
            }
624
            // Adding menu item:
625
            $menuItems[] = array_merge(
626
                array_merge($currentPageWithNoOverlay, $lRecs),
0 ignored issues
show
Bug introduced by
It seems like $currentPageWithNoOverlay can also be of type integer; however, parameter $arrays 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

626
                array_merge(/** @scrutinizer ignore-type */ $currentPageWithNoOverlay, $lRecs),
Loading history...
627
                [
628
                    '_PAGES_OVERLAY_REQUESTEDLANGUAGE' => $sUid,
629
                    'ITEM_STATE' => $iState,
630
                    '_ADD_GETVARS' => $getVars,
631
                    '_SAFE' => true
632
                ]
633
            );
634
        }
635
        return $menuItems;
636
    }
637
638
    /**
639
     * Fetches all menuitems if special = directory is set
640
     *
641
     * @param string $specialValue The value from special.value
642
     * @param string $sortingField The sorting field
643
     * @return array
644
     */
645
    protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
646
    {
647
        $tsfe = $this->getTypoScriptFrontendController();
648
        $menuItems = [];
649
        if ($specialValue == '') {
650
            $specialValue = $tsfe->page['uid'];
651
        }
652
        $items = GeneralUtility::intExplode(',', $specialValue);
653
        $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
654
        foreach ($items as $id) {
655
            $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($id);
656
            // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
657
            $mount_info = $this->sys_page->getMountPointInfo($id);
658
            if (is_array($mount_info)) {
659
                if ($mount_info['overlay']) {
660
                    // Overlays should already have their full MPvars calculated:
661
                    $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid']);
662
                    $MP = $MP ?: $mount_info['MPvar'];
663
                } else {
664
                    $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
665
                }
666
                $id = $mount_info['mount_pid'];
667
            }
668
            // Get sub-pages:
669
            $statement = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
670
            while ($row = $statement->fetch()) {
671
                // When the site language configuration is in "free" mode, then the page without overlay is fetched
672
                // (which is kind-of strange for pages, but this is what exec_getQuery() is doing)
673
                // this means, that $row is a translated page, but hasn't been overlaid. For this reason, we fetch
674
                // the default translation page again, (which does a ->getPageOverlay() again - doing this on a
675
                // translated page would result in no record at all)
676
                if ($row['l10n_parent'] > 0 && !isset($row['_PAGES_OVERLAY'])) {
677
                    $row = $this->sys_page->getPage($row['l10n_parent'], true);
678
                }
679
                $tsfe->sys_page->versionOL('pages', $row, true);
680
                if (!empty($row)) {
681
                    // Keep mount point?
682
                    $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
683
                    // There is a valid mount point.
684
                    if (is_array($mount_info) && $mount_info['overlay']) {
685
                        // Using "getPage" is OK since we need the check for enableFields
686
                        // AND for type 2 of mount pids we DO require a doktype < 200!
687
                        $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
688
                        if (!empty($mp_row)) {
689
                            $row = $mp_row;
690
                            $row['_MP_PARAM'] = $mount_info['MPvar'];
691
                        } else {
692
                            // If the mount point could not be fetched with respect
693
                            // to enableFields, unset the row so it does not become a part of the menu!
694
                            unset($row);
695
                        }
696
                    }
697
                    // Add external MP params, then the row:
698
                    if (!empty($row)) {
699
                        if ($MP) {
700
                            $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
701
                        }
702
                        $menuItems[] = $this->sys_page->getPageOverlay($row);
703
                    }
704
                }
705
            }
706
        }
707
708
        return $menuItems;
709
    }
710
711
    /**
712
     * Fetches all menuitems if special = list is set
713
     *
714
     * @param string $specialValue The value from special.value
715
     * @return array
716
     */
717
    protected function prepareMenuItemsForListMenu($specialValue)
718
    {
719
        $menuItems = [];
720
        if ($specialValue == '') {
721
            $specialValue = $this->id;
722
        }
723
        $skippedEnableFields = [];
724
        if (!empty($this->mconf['showAccessRestrictedPages'])) {
725
            $skippedEnableFields = ['fe_group' => 1];
726
        }
727
        /** @var RelationHandler $loadDB*/
728
        $loadDB = GeneralUtility::makeInstance(RelationHandler::class);
729
        $loadDB->setFetchAllFields(true);
730
        $loadDB->start($specialValue, 'pages');
731
        $loadDB->additionalWhere['pages'] = $this->sys_page->enableFields('pages', -1, $skippedEnableFields);
732
        $loadDB->getFromDB();
733
        $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
734
        foreach ($loadDB->itemArray as $val) {
735
            $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$val['id']);
736
            // Keep mount point?
737
            $mount_info = $this->sys_page->getMountPointInfo($val['id']);
738
            // There is a valid mount point.
739
            if (is_array($mount_info) && $mount_info['overlay']) {
740
                // Using "getPage" is OK since we need the check for enableFields
741
                // AND for type 2 of mount pids we DO require a doktype < 200!
742
                $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
743
                if (!empty($mp_row)) {
744
                    $row = $mp_row;
745
                    $row['_MP_PARAM'] = $mount_info['MPvar'];
746
                    // Overlays should already have their full MPvars calculated
747
                    if ($mount_info['overlay']) {
748
                        $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid']);
749
                        if ($MP) {
750
                            unset($row['_MP_PARAM']);
751
                        }
752
                    }
753
                } else {
754
                    // If the mount point could not be fetched with respect to
755
                    // enableFields, unset the row so it does not become a part of the menu!
756
                    unset($row);
757
                }
758
            } else {
759
                $row = $loadDB->results['pages'][$val['id']];
760
            }
761
            // Add versioning overlay for current page (to respect workspaces)
762
            if (isset($row) && is_array($row)) {
763
                $this->sys_page->versionOL('pages', $row, true);
764
            }
765
            // Add external MP params, then the row:
766
            if (isset($row) && is_array($row)) {
767
                if ($MP) {
768
                    $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
769
                }
770
                $menuItems[] = $this->sys_page->getPageOverlay($row);
771
            }
772
        }
773
        return $menuItems;
774
    }
775
776
    /**
777
     * Fetches all menuitems if special = updated is set
778
     *
779
     * @param string $specialValue The value from special.value
780
     * @param string $sortingField The sorting field
781
     * @return array
782
     */
783
    protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
784
    {
785
        $tsfe = $this->getTypoScriptFrontendController();
786
        $menuItems = [];
787
        if ($specialValue == '') {
788
            $specialValue = $tsfe->page['uid'];
789
        }
790
        $items = GeneralUtility::intExplode(',', $specialValue);
791
        if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
792
            $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
793
        } else {
794
            $depth = 20;
795
        }
796
        // Max number of items
797
        $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
798
        $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
799
        if (!$limit) {
800
            $limit = 10;
801
        }
802
        // 'auto', 'manual', 'tstamp'
803
        $mode = $this->conf['special.']['mode'];
804
        // Get id's
805
        $beginAtLevel = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
806
        $id_list_arr = [];
807
        foreach ($items as $id) {
808
            // Exclude the current ID if beginAtLevel is > 0
809
            if ($beginAtLevel > 0) {
810
                $id_list_arr[] = $this->parent_cObj->getTreeList($id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
811
            } else {
812
                $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
813
            }
814
        }
815
        $id_list = implode(',', $id_list_arr);
816
        // Get sortField (mode)
817
        switch ($mode) {
818
            case 'starttime':
819
                $sortField = 'starttime';
820
                break;
821
            case 'lastUpdated':
822
            case 'manual':
823
                $sortField = 'lastUpdated';
824
                break;
825
            case 'tstamp':
826
                $sortField = 'tstamp';
827
                break;
828
            case 'crdate':
829
                $sortField = 'crdate';
830
                break;
831
            default:
832
                $sortField = 'SYS_LASTCHANGED';
833
        }
834
        $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
835
        if ($this->conf['special.']['excludeNoSearchPages']) {
836
            $extraWhere .= ' AND pages.no_search=0';
837
        }
838
        if ($maxAge > 0) {
839
            $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
840
        }
841
        $statement = $this->parent_cObj->exec_getQuery('pages', [
842
            'pidInList' => '0',
843
            'uidInList' => $id_list,
844
            'where' => $sortField . '>=0' . $extraWhere,
845
            'orderBy' => $sortingField ?: $sortField . ' DESC',
846
            'max' => $limit
847
        ]);
848
        while ($row = $statement->fetch()) {
849
            // When the site language configuration is in "free" mode, then the page without overlay is fetched
850
            // (which is kind-of strange for pages, but this is what exec_getQuery() is doing)
851
            // this means, that $row is a translated page, but hasn't been overlaid. For this reason, we fetch
852
            // the default translation page again, (which does a ->getPageOverlay() again - doing this on a
853
            // translated page would result in no record at all)
854
            if ($row['l10n_parent'] > 0 && !isset($row['_PAGES_OVERLAY'])) {
855
                $row = $this->sys_page->getPage($row['l10n_parent'], true);
856
            }
857
            $tsfe->sys_page->versionOL('pages', $row, true);
858
            if (is_array($row)) {
859
                $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
860
            }
861
        }
862
863
        return $menuItems;
864
    }
865
866
    /**
867
     * Fetches all menuitems if special = keywords is set
868
     *
869
     * @param string $specialValue The value from special.value
870
     * @param string $sortingField The sorting field
871
     * @return array
872
     */
873
    protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
874
    {
875
        $tsfe = $this->getTypoScriptFrontendController();
876
        $menuItems = [];
877
        [$specialValue] = GeneralUtility::intExplode(',', $specialValue);
878
        if (!$specialValue) {
879
            $specialValue = $tsfe->page['uid'];
880
        }
881
        if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
882
            $kw = (string)$this->parent_cObj->stdWrapValue('setKeywords', $this->conf['special.'] ?? []);
883
        } else {
884
            // The page record of the 'value'.
885
            $value_rec = $this->sys_page->getPage($specialValue);
886
            $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ?: 'keywords';
887
            // keywords.
888
            $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
889
        }
890
        // *'auto', 'manual', 'tstamp'
891
        $mode = $this->conf['special.']['mode'];
892
        switch ($mode) {
893
            case 'starttime':
894
                $sortField = 'starttime';
895
                break;
896
            case 'lastUpdated':
897
            case 'manual':
898
                $sortField = 'lastUpdated';
899
                break;
900
            case 'tstamp':
901
                $sortField = 'tstamp';
902
                break;
903
            case 'crdate':
904
                $sortField = 'crdate';
905
                break;
906
            default:
907
                $sortField = 'SYS_LASTCHANGED';
908
        }
909
        // Depth, limit, extra where
910
        if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
911
            $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
912
        } else {
913
            $depth = 20;
914
        }
915
        // Max number of items
916
        $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
917
        // Start point
918
        $eLevel = $this->parent_cObj->getKey(
919
            $this->parent_cObj->stdWrapValue('entryLevel', $this->conf['special.'] ?? []),
0 ignored issues
show
Bug introduced by
It seems like $this->parent_cObj->stdW...'special.'] ?? array()) can also be of type boolean and 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

919
            /** @scrutinizer ignore-type */ $this->parent_cObj->stdWrapValue('entryLevel', $this->conf['special.'] ?? []),
Loading history...
920
            $this->tmpl->rootLine
921
        );
922
        $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
923
        // Which field is for keywords
924
        $kfield = 'keywords';
925
        if ($this->conf['special.']['keywordsField']) {
926
            [$kfield] = explode(' ', trim($this->conf['special.']['keywordsField']));
927
        }
928
        // If there are keywords and the startuid is present
929
        if ($kw && $startUid) {
930
            $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
931
            $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
932
            $kwArr = GeneralUtility::trimExplode(',', $kw, true);
933
            $keyWordsWhereArr = [];
934
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
935
            foreach ($kwArr as $word) {
936
                $keyWordsWhereArr[] = $queryBuilder->expr()->like(
937
                    $kfield,
938
                    $queryBuilder->createNamedParameter(
939
                        '%' . $queryBuilder->escapeLikeWildcards($word) . '%',
940
                        \PDO::PARAM_STR
941
                    )
942
                );
943
            }
944
            $queryBuilder
945
                ->select('*')
946
                ->from('pages')
947
                ->where(
948
                    $queryBuilder->expr()->in(
949
                        'uid',
950
                        GeneralUtility::intExplode(',', $id_list, true)
951
                    ),
952
                    $queryBuilder->expr()->neq(
953
                        'uid',
954
                        $queryBuilder->createNamedParameter($specialValue, \PDO::PARAM_INT)
955
                    )
956
                );
957
958
            if (!empty($keyWordsWhereArr)) {
959
                $queryBuilder->andWhere($queryBuilder->expr()->orX(...$keyWordsWhereArr));
960
            }
961
962
            if (!empty($this->excludedDoktypes)) {
963
                $queryBuilder->andWhere(
964
                    $queryBuilder->expr()->notIn(
965
                        'pages.doktype',
966
                        $this->excludedDoktypes
967
                    )
968
                );
969
            }
970
971
            if (!$this->conf['includeNotInMenu']) {
972
                $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.nav_hide', 0));
973
            }
974
975
            if ($this->conf['special.']['excludeNoSearchPages']) {
976
                $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.no_search', 0));
977
            }
978
979
            if ($limit > 0) {
980
                $queryBuilder->setMaxResults($limit);
981
            }
982
983
            if ($sortingField) {
984
                $queryBuilder->orderBy($sortingField);
985
            } else {
986
                $queryBuilder->orderBy($sortField, 'desc');
987
            }
988
989
            $result = $queryBuilder->execute();
990
            while ($row = $result->fetch()) {
991
                $tsfe->sys_page->versionOL('pages', $row, true);
992
                if (is_array($row)) {
993
                    $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
994
                }
995
            }
996
        }
997
998
        return $menuItems;
999
    }
1000
1001
    /**
1002
     * Fetches all menuitems if special = rootline is set
1003
     *
1004
     * @return array
1005
     */
1006
    protected function prepareMenuItemsForRootlineMenu()
1007
    {
1008
        $menuItems = [];
1009
        $range = (string)$this->parent_cObj->stdWrapValue('range', $this->conf['special.'] ?? []);
1010
        $begin_end = explode('|', $range);
1011
        $begin_end[0] = (int)$begin_end[0];
1012
        if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1013
            $begin_end[1] = -1;
1014
        }
1015
        $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1016
        $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1017
        if ($endKey < $beginKey) {
1018
            $endKey = $beginKey;
1019
        }
1020
        $rl_MParray = [];
1021
        foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1022
            // For overlaid mount points, set the variable right now:
1023
            if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1024
                $rl_MParray[] = $v_rl['_MP_PARAM'];
1025
            }
1026
            // Traverse rootline:
1027
            if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1028
                $temp_key = $k_rl;
1029
                $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1030
                if (!empty($menuItems[$temp_key])) {
1031
                    // If there are no specific target for the page, put the level specific target on.
1032
                    if (!$menuItems[$temp_key]['target']) {
1033
                        $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1034
                        $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1035
                    }
1036
                } else {
1037
                    unset($menuItems[$temp_key]);
1038
                }
1039
            }
1040
            // For normal mount points, set the variable for next level.
1041
            if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1042
                $rl_MParray[] = $v_rl['_MP_PARAM'];
1043
            }
1044
        }
1045
        // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1046
        if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1047
            $menuItems = array_reverse($menuItems);
1048
        }
1049
        return $menuItems;
1050
    }
1051
1052
    /**
1053
     * Fetches all menuitems if special = browse is set
1054
     *
1055
     * @param string $specialValue The value from special.value
1056
     * @param string $sortingField The sorting field
1057
     * @param string $additionalWhere Additional WHERE clause
1058
     * @return array
1059
     */
1060
    protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1061
    {
1062
        $menuItems = [];
1063
        [$specialValue] = GeneralUtility::intExplode(',', $specialValue);
1064
        if (!$specialValue) {
1065
            $specialValue = $this->getTypoScriptFrontendController()->page['uid'];
1066
        }
1067
        // Will not work out of rootline
1068
        if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1069
            $recArr = [];
1070
            // The page record of the 'value'.
1071
            $value_rec = $this->sys_page->getPage($specialValue);
1072
            // 'up' page cannot be outside rootline
1073
            if ($value_rec['pid']) {
1074
                // The page record of 'up'.
1075
                $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1076
            }
1077
            // If the 'up' item was NOT level 0 in rootline...
1078
            if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1079
                // The page record of "index".
1080
                $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1081
            }
1082
            // check if certain pages should be excluded
1083
            $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1084
            if ($this->conf['special.']['excludeNoSearchPages']) {
1085
                $additionalWhere .= ' AND pages.no_search=0';
1086
            }
1087
            // prev / next is found
1088
            $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1089
            $lastKey = 0;
1090
            $nextActive = 0;
1091
            foreach ($prevnext_menu as $k_b => $v_b) {
1092
                if ($nextActive) {
1093
                    $recArr['next'] = $v_b;
1094
                    $nextActive = 0;
1095
                }
1096
                if ($v_b['uid'] == $specialValue) {
1097
                    if ($lastKey) {
1098
                        $recArr['prev'] = $prevnext_menu[$lastKey];
1099
                    }
1100
                    $nextActive = 1;
1101
                }
1102
                $lastKey = $k_b;
1103
            }
1104
1105
            $recArr['first'] = reset($prevnext_menu);
1106
            $recArr['last'] = end($prevnext_menu);
1107
            // prevsection / nextsection is found
1108
            // You can only do this, if there is a valid page two levels up!
1109
            if (!empty($recArr['index']['uid'])) {
1110
                $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1111
                $lastKey = 0;
1112
                $nextActive = 0;
1113
                foreach ($prevnextsection_menu as $k_b => $v_b) {
1114
                    if ($nextActive) {
1115
                        $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1116
                        if (!empty($sectionRec_temp)) {
1117
                            $recArr['nextsection'] = reset($sectionRec_temp);
1118
                            $recArr['nextsection_last'] = end($sectionRec_temp);
1119
                            $nextActive = 0;
1120
                        }
1121
                    }
1122
                    if ($v_b['uid'] == $value_rec['pid']) {
1123
                        if ($lastKey) {
1124
                            $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1125
                            if (!empty($sectionRec_temp)) {
1126
                                $recArr['prevsection'] = reset($sectionRec_temp);
1127
                                $recArr['prevsection_last'] = end($sectionRec_temp);
1128
                            }
1129
                        }
1130
                        $nextActive = 1;
1131
                    }
1132
                    $lastKey = $k_b;
1133
                }
1134
            }
1135
            if ($this->conf['special.']['items.']['prevnextToSection']) {
1136
                if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1137
                    $recArr['prev'] = $recArr['prevsection_last'];
1138
                }
1139
                if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1140
                    $recArr['next'] = $recArr['nextsection'];
1141
                }
1142
            }
1143
            $items = explode('|', $this->conf['special.']['items']);
1144
            $c = 0;
1145
            foreach ($items as $k_b => $v_b) {
1146
                $v_b = strtolower(trim($v_b));
1147
                if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1148
                    $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1149
                }
1150
                if (is_array($recArr[$v_b])) {
1151
                    $menuItems[$c] = $recArr[$v_b];
1152
                    if ($this->conf['special.'][$v_b . '.']['target']) {
1153
                        $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1154
                    }
1155
                    $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1156
                    if (is_array($tmpSpecialFields)) {
1157
                        foreach ($tmpSpecialFields as $fk => $val) {
1158
                            $menuItems[$c][$fk] = $val;
1159
                        }
1160
                    }
1161
                    $c++;
1162
                }
1163
            }
1164
        }
1165
        return $menuItems;
1166
    }
1167
1168
    /**
1169
     * Checks if a page is OK to include in the final menu item array. Pages can be excluded if the doktype is wrong,
1170
     * if they are hidden in navigation, have a uid in the list of banned uids etc.
1171
     *
1172
     * @param array $data Array of menu items
1173
     * @param array $banUidArray Array of page uids which are to be excluded
1174
     * @param bool $isSpacerPage If set, then the page is a spacer.
1175
     * @return bool Returns TRUE if the page can be safely included.
1176
     *
1177
     * @throws \UnexpectedValueException
1178
     */
1179
    public function filterMenuPages(&$data, $banUidArray, $isSpacerPage)
1180
    {
1181
        $includePage = true;
1182
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] ?? [] as $className) {
1183
            $hookObject = GeneralUtility::makeInstance($className);
1184
            if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1185
                throw new \UnexpectedValueException($className . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1186
            }
1187
            $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $isSpacerPage, $this);
1188
        }
1189
        if (!$includePage) {
1190
            return false;
1191
        }
1192
        if ($data['_SAFE']) {
1193
            return true;
1194
        }
1195
        // If the spacer-function is not enabled, spacers will not enter the $menuArr
1196
        if (!$this->mconf['SPC'] && $isSpacerPage) {
1197
            return false;
1198
        }
1199
        // Page may not be a 'Backend User Section' or any other excluded doktype
1200
        if (in_array((int)$data['doktype'], $this->excludedDoktypes, true)) {
1201
            return false;
1202
        }
1203
        // PageID should not be banned
1204
        if (in_array((int)$data['uid'], $banUidArray, true)) {
1205
            return false;
1206
        }
1207
        // If the page is hide in menu, but the menu does not include them do not show the page
1208
        if ($data['nav_hide'] && !$this->conf['includeNotInMenu']) {
1209
            return false;
1210
        }
1211
        // Checking if a page should be shown in the menu depending on whether a translation exists or if the default language is disabled
1212
        if (!$this->sys_page->isPageSuitableForLanguage($data, $this->getCurrentLanguageAspect())) {
1213
            return false;
1214
        }
1215
        // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1216
        if ($this->getCurrentLanguageAspect()->getId() > 0 && $this->conf['protectLvar']) {
1217
            $pageTranslationVisibility = new PageTranslationVisibility((int)($data['l18n_cfg'] ?? 0));
1218
            if ($this->conf['protectLvar'] === 'all' || $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()) {
1219
                $olRec = $this->sys_page->getPageOverlay($data['uid'], $this->getCurrentLanguageAspect()->getId());
1220
                if (empty($olRec)) {
1221
                    // If no page translation record then page can NOT be accessed in
1222
                    // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1223
                    $data['_ADD_GETVARS'] .= '&L=0';
1224
                }
1225
            }
1226
        }
1227
        return true;
1228
    }
1229
1230
    /**
1231
     * Generating the per-menu-item configuration arrays based on the settings for item states (NO, ACT, CUR etc)
1232
     * set in ->mconf (config for the current menu object)
1233
     * Basically it will produce an individual array for each menu item based on the item states.
1234
     * BUT in addition the "optionSplit" syntax for the values is ALSO evaluated here so that all property-values
1235
     * are "option-splitted" and the output will thus be resolved.
1236
     * Is called from the "generate" functions in the extension classes. The function is processor intensive due to
1237
     * the option split feature in particular. But since the generate function is not always called
1238
     * (since the ->result array may be cached, see makeMenu) it doesn't hurt so badly.
1239
     *
1240
     * @param int $splitCount Number of menu items in the menu
1241
     * @return array the resolved configuration for each item
1242
     */
1243
    protected function processItemStates($splitCount)
1244
    {
1245
        // Prepare normal settings
1246
        if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1247
            // Setting a blank array if NO=1 and there are no properties.
1248
            $this->mconf['NO.'] = [];
1249
        }
1250
        $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1251
        $NOconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['NO.'], $splitCount);
1252
1253
        // Prepare custom states settings, overriding normal settings
1254
        foreach (self::customItemStates as $state) {
1255
            if (empty($this->mconf[$state])) {
1256
                continue;
1257
            }
1258
            $customConfiguration = null;
1259
            foreach ($NOconf as $key => $val) {
1260
                if ($this->isItemState($state, $key)) {
1261
                    // if this is the first element of type $state, we must generate the custom configuration.
1262
                    if ($customConfiguration === null) {
1263
                        $customConfiguration = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf[$state . '.'], $splitCount);
1264
                    }
1265
                    // Substitute normal with the custom (e.g. IFSUB)
1266
                    if (isset($customConfiguration[$key])) {
1267
                        $NOconf[$key] = $customConfiguration[$key];
1268
                    }
1269
                }
1270
            }
1271
        }
1272
1273
        return $NOconf;
1274
    }
1275
1276
    /**
1277
     * 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
1278
     * This function doesn't care about the url, because if we let the url be redirected, it will be logged in the stat!!!
1279
     *
1280
     * @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)
1281
     * @param string $altTarget Alternative target
1282
     * @param string $typeOverride Alternative type
1283
     * @return array Returns an array with A-tag attributes as key/value pairs (HREF, TARGET and onClick)
1284
     */
1285
    protected function link($key, $altTarget, $typeOverride)
1286
    {
1287
        $runtimeCache = $this->getRuntimeCache();
1288
        $MP_var = $this->getMPvar($key);
1289
        $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . $MP_var . json_encode($this->menuArr[$key]));
1290
        $runtimeCachedLink = $runtimeCache->get($cacheId);
1291
        if ($runtimeCachedLink !== false) {
1292
            return $runtimeCachedLink;
1293
        }
1294
1295
        $tsfe = $this->getTypoScriptFrontendController();
1296
1297
        // If a user script returned the value overrideId in the menu array we use that as page id
1298
        if (($this->mconf['overrideId'] ?? false) || ($this->menuArr[$key]['overrideId'] ?? false)) {
1299
            $overrideId = (int)($this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId']);
1300
            $overrideId = $overrideId > 0 ? $overrideId : null;
1301
            // Clear MP parameters since ID was changed.
1302
            $MP_params = '';
1303
        } else {
1304
            $overrideId = null;
1305
            // Mount points:
1306
            $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1307
        }
1308
        // Setting main target:
1309
        if ($altTarget) {
1310
            $mainTarget = $altTarget;
1311
        } else {
1312
            $mainTarget = (string)$this->parent_cObj->stdWrapValue('target', $this->mconf ?? []);
1313
        }
1314
        // Creating link:
1315
        $addParams = ($this->mconf['addParams'] ?? '') . $MP_params;
1316
        if (($this->mconf['collapse'] ?? false) && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1317
            $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1318
            $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1319
            $LD = $this->menuTypoLink($thePage, $mainTarget, $addParams, $typeOverride, $overrideId);
1320
        } else {
1321
            $addParams .= ($this->I['val']['additionalParams'] ?? '') . $this->menuArr[$key]['_ADD_GETVARS'];
1322
            $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, $addParams, $typeOverride, $overrideId);
1323
        }
1324
        // Override default target configuration if option is set
1325
        if ($this->menuArr[$key]['target']) {
1326
            $LD['target'] = $this->menuArr[$key]['target'];
1327
        }
1328
        // Override URL if using "External URL"
1329
        if ((int)$this->menuArr[$key]['doktype'] === PageRepository::DOKTYPE_LINK) {
1330
            $externalUrl = (string)$this->sys_page->getExtURL($this->menuArr[$key]);
1331
            // Create link using typolink (concerning spamProtectEmailAddresses) for email links
1332
            $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $externalUrl]);
1333
            // Links to emails should not have any target
1334
            if (stripos($externalUrl, 'mailto:') === 0) {
1335
                $LD['target'] = '';
1336
            // use external target for the URL
1337
            } elseif (empty($LD['target']) && !empty($tsfe->extTarget)) {
1338
                $LD['target'] = $tsfe->extTarget;
1339
            }
1340
        }
1341
1342
        // Override url if current page is a shortcut
1343
        $shortcut = null;
1344
        if ((int)$this->menuArr[$key]['doktype'] === PageRepository::DOKTYPE_SHORTCUT && (int)$this->menuArr[$key]['shortcut_mode'] !== PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1345
            $menuItem = $this->menuArr[$key];
1346
            try {
1347
                $shortcut = $tsfe->sys_page->getPageShortcut(
1348
                    $menuItem['shortcut'],
1349
                    $menuItem['shortcut_mode'],
1350
                    $menuItem['uid'],
1351
                    20,
1352
                    [],
1353
                    true
1354
                );
1355
                if (isset($menuItem['_PAGES_OVERLAY_LANGUAGE'])) {
1356
                    $shortcut = $tsfe->sys_page->getPageOverlay($shortcut, $menuItem['_PAGES_OVERLAY_LANGUAGE']);
1357
                }
1358
            } catch (\Exception $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1359
            }
1360
            if (!is_array($shortcut)) {
1361
                $runtimeCache->set($cacheId, []);
1362
                return [];
1363
            }
1364
            // Only setting url, not target
1365
            $LD['totalURL'] = $this->parent_cObj->typoLink_URL([
1366
                'parameter' => $shortcut['uid'],
1367
                'language' => $shortcut['_PAGES_OVERLAY_REQUESTEDLANGUAGE'] ?? 'current',
1368
                'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1369
                'linkAccessRestrictedPages' => !empty($this->mconf['showAccessRestrictedPages'])
1370
            ]);
1371
        }
1372
        if ($shortcut) {
1373
            $pageData = $shortcut;
1374
            $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1375
        } else {
1376
            $pageData = $this->menuArr[$key];
1377
        }
1378
        // Manipulation in case of access restricted pages:
1379
        $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1380
        // Overriding URL / Target if set to do so:
1381
        if ($this->menuArr[$key]['_OVERRIDE_HREF'] ?? false) {
1382
            $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1383
            if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1384
                $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1385
            }
1386
        }
1387
        // OnClick open in windows.
1388
        $onClick = '';
1389
        if ($this->mconf['JSWindow'] ?? false) {
1390
            $conf = $this->mconf['JSWindow.'];
1391
            $url = $LD['totalURL'];
1392
            $LD['totalURL'] = '#';
1393
            $onClick = 'openPic('
1394
                . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1395
                . '\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1396
                . GeneralUtility::quoteJSvalue($conf['params']) . '); return false;';
1397
            GeneralUtility::makeInstance(AssetCollector::class)->addInlineJavaScript('openPic', 'function openPic(url, winName, winParams) { var theWindow = window.open(url, winName, winParams); if (theWindow) { theWindow.focus(); } }');
1398
        }
1399
        // look for type and popup
1400
        // following settings are valid in field target:
1401
        // 230								will add type=230 to the link
1402
        // 230 500x600						will add type=230 to the link and open in popup window with 500x600 pixels
1403
        // 230 _blank						will add type=230 to the link and open with target "_blank"
1404
        // 230x450:resizable=0,location=1	will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1405
        $matches = [];
1406
        $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1407
        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 false|integer 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...
1408
            // has type?
1409
            if ((int)($matches[1] ?? 0) || $targetIsType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $targetIsType of type false|integer 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...
1410
                $LD['totalURL'] .= (strpos($LD['totalURL'], '?') === false ? '?' : '&') . 'type=' . ($targetIsType ?: (int)$matches[1]);
1411
                $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1412
            }
1413
            // Open in popup window?
1414
            if (($matches[3] ?? false) && ($matches[4] ?? false)) {
1415
                $target = $LD['target'] ?? 'FEopenLink';
1416
                $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1417
                $onClick = 'vHWin=window.open('
1418
                    . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1419
                    . ',' . GeneralUtility::quoteJSvalue($target) . ',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1420
                $LD['target'] = '';
1421
            }
1422
        }
1423
        // out:
1424
        $list = [];
1425
        // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1426
        // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1427
        // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1428
        $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1429
        $list['TARGET'] = $LD['target'];
1430
        $list['onClick'] = $onClick;
1431
        $runtimeCache->set($cacheId, $list);
1432
        return $list;
1433
    }
1434
1435
    /**
1436
     * Determines original shortcut destination in page overlays.
1437
     *
1438
     * Since the pages records used for menu rendering are overlaid by default,
1439
     * the original 'shortcut' value is lost, if a translation did not define one.
1440
     *
1441
     * @param array $page
1442
     * @return array
1443
     */
1444
    protected function determineOriginalShortcutPage(array $page)
1445
    {
1446
        // Check if modification is required
1447
        if (
1448
            $this->getCurrentLanguageAspect()->getId() > 0
1449
            && empty($page['shortcut'])
1450
            && !empty($page['uid'])
1451
            && !empty($page['_PAGES_OVERLAY'])
1452
            && !empty($page['_PAGES_OVERLAY_UID'])
1453
        ) {
1454
            // Using raw record since the record was overlaid and is correct already:
1455
            $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1456
1457
            if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1458
                $page['shortcut'] = $originalPage['shortcut'];
1459
            }
1460
        }
1461
1462
        return $page;
1463
    }
1464
1465
    /**
1466
     * Will change $LD (passed by reference) if the page is access restricted
1467
     *
1468
     * @param array $LD The array from the linkData() function
1469
     * @param array $page Page array
1470
     * @param string $mainTarget Main target value
1471
     * @param string $typeOverride Type number override if any
1472
     */
1473
    protected function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1474
    {
1475
        // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1476
        if ($this->mconf['showAccessRestrictedPages'] ?? false && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1477
            $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1478
            $addParams = str_replace(
1479
                [
1480
                    '###RETURN_URL###',
1481
                    '###PAGE_ID###'
1482
                ],
1483
                [
1484
                    rawurlencode($LD['totalURL']),
1485
                    $page['_SHORTCUT_PAGE_UID'] ?? $page['uid']
1486
                ],
1487
                $this->mconf['showAccessRestrictedPages.']['addParams']
1488
            );
1489
            $LD = $this->menuTypoLink($thePage, $mainTarget, $addParams, $typeOverride);
1490
        }
1491
    }
1492
1493
    /**
1494
     * Creates a submenu level to the current level - if configured for.
1495
     *
1496
     * @param int $uid Page id of the current page for which a submenu MAY be produced (if conditions are met)
1497
     * @param string $objSuffix Object prefix, see ->start()
1498
     * @return string HTML content of the submenu
1499
     */
1500
    protected function subMenu($uid, $objSuffix)
1501
    {
1502
        // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1503
        $altArray = '';
1504
        if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU'] ?? null) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1505
            $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1506
        }
1507
        // Make submenu if the page is the next active
1508
        $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix] ?? '';
1509
        // stdWrap for expAll
1510
        $this->mconf['expAll'] = $this->parent_cObj->stdWrapValue('expAll', $this->mconf ?? []);
1511
        if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !($this->mconf['sectionIndex'] ?? false)) {
1512
            try {
1513
                $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1514
                /** @var AbstractMenuContentObject $submenu */
1515
                $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1516
                $submenu->entryLevel = $this->entryLevel + 1;
1517
                $submenu->rL_uidRegister = $this->rL_uidRegister;
1518
                $submenu->MP_array = $this->MP_array;
1519
                if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1520
                    $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1521
                }
1522
                // Especially scripts that build the submenu needs the parent data
1523
                $submenu->parent_cObj = $this->parent_cObj;
1524
                $submenu->setParentMenu($this->menuArr, $this->I['key']);
1525
                // Setting alternativeMenuTempArray (will be effective only if an array and not empty)
1526
                if (is_array($altArray) && !empty($altArray)) {
1527
                    $submenu->alternativeMenuTempArray = $altArray;
1528
                }
1529
                if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1530
                    $submenu->makeMenu();
1531
                    // Memorize the current menu item count
1532
                    $tsfe = $this->getTypoScriptFrontendController();
1533
                    $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1534
                    // Reset the menu item count for the submenu
1535
                    $tsfe->register['count_MENUOBJ'] = 0;
1536
                    $content = $submenu->writeMenu();
1537
                    // Restore the item count now that the submenu has been handled
1538
                    $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1539
                    $tsfe->register['count_menuItems'] = count($this->menuArr);
1540
                    return $content;
1541
                }
1542
            } catch (NoSuchMenuTypeException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1543
            }
1544
        }
1545
        return '';
1546
    }
1547
1548
    /**
1549
     * Returns TRUE if the page with UID $uid is the NEXT page in root line (which means a submenu should be drawn)
1550
     *
1551
     * @param int $uid Page uid to evaluate.
1552
     * @param string $MPvar MPvar for the current position of item.
1553
     * @return bool TRUE if page with $uid is active
1554
     * @see subMenu()
1555
     */
1556
    protected function isNext($uid, $MPvar)
1557
    {
1558
        // Check for always active PIDs:
1559
        if (in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1560
            return true;
1561
        }
1562
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1563
        if ($uid && $testUid == $this->nextActive) {
1564
            return true;
1565
        }
1566
        return false;
1567
    }
1568
1569
    /**
1570
     * Returns TRUE if the page with UID $uid is active (in the current rootline)
1571
     *
1572
     * @param int $uid Page uid to evaluate.
1573
     * @param string $MPvar MPvar for the current position of item.
1574
     * @return bool TRUE if page with $uid is active
1575
     */
1576
    protected function isActive($uid, $MPvar)
1577
    {
1578
        // Check for always active PIDs:
1579
        if (in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1580
            return true;
1581
        }
1582
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1583
        if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1584
            return true;
1585
        }
1586
        return false;
1587
    }
1588
1589
    /**
1590
     * Returns TRUE if the page with UID $uid is the CURRENT page (equals $this->getTypoScriptFrontendController()->id)
1591
     *
1592
     * @param int $uid Page uid to evaluate.
1593
     * @param string $MPvar MPvar for the current position of item.
1594
     * @return bool TRUE if page $uid = $this->getTypoScriptFrontendController()->id
1595
     */
1596
    protected function isCurrent($uid, $MPvar)
1597
    {
1598
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1599
        return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1600
    }
1601
1602
    /**
1603
     * Returns TRUE if there is a submenu with items for the page id, $uid
1604
     * Used by the item states "IFSUB", "ACTIFSUB" and "CURIFSUB" to check if there is a submenu
1605
     *
1606
     * @param int $uid Page uid for which to search for a submenu
1607
     * @return bool Returns TRUE if there was a submenu with items found
1608
     */
1609
    protected function isSubMenu($uid)
1610
    {
1611
        $cacheId = 'menucontentobject-is-submenu-decision-' . $uid;
1612
        $runtimeCache = $this->getRuntimeCache();
1613
        $cachedDecision = $runtimeCache->get($cacheId);
1614
        if (isset($cachedDecision['result'])) {
1615
            return $cachedDecision['result'];
1616
        }
1617
        // Looking for a mount-pid for this UID since if that
1618
        // exists we should look for a subpages THERE and not in the input $uid;
1619
        $mount_info = $this->sys_page->getMountPointInfo($uid);
1620
        if (is_array($mount_info)) {
1621
            $uid = $mount_info['mount_pid'];
1622
        }
1623
        $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1624
        $hasSubPages = false;
1625
        $bannedUids = $this->getBannedUids();
1626
        $languageId = $this->getCurrentLanguageAspect()->getId();
1627
        foreach ($recs as $theRec) {
1628
            // no valid subpage if the document type is excluded from the menu
1629
            if (in_array((int)($theRec['doktype'] ?? 0), $this->excludedDoktypes, true)) {
1630
                continue;
1631
            }
1632
            // No valid subpage if the page is hidden inside menus and
1633
            // it wasn't forced to show such entries
1634
            if (isset($theRec['nav_hide']) && $theRec['nav_hide']
1635
                && (!isset($this->conf['includeNotInMenu']) || !$this->conf['includeNotInMenu'])
1636
            ) {
1637
                continue;
1638
            }
1639
            // No valid subpage if the default language should be shown and the page settings
1640
            // are excluding the visibility of the default language
1641
            $pageTranslationVisibility = new PageTranslationVisibility((int)($theRec['l18n_cfg'] ?? 0));
1642
            if (!$languageId && $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage()) {
1643
                continue;
1644
            }
1645
            // No valid subpage if the alternative language should be shown and the page settings
1646
            // are requiring a valid overlay but it doesn't exists
1647
            if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() && $languageId > 0 && !$theRec['_PAGES_OVERLAY']) {
1648
                continue;
1649
            }
1650
            // No valid subpage if the subpage is banned by excludeUidList
1651
            if (in_array((int)$theRec['uid'], $bannedUids, true)) {
1652
                continue;
1653
            }
1654
            $hasSubPages = true;
1655
            break;
1656
        }
1657
        $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1658
        return $hasSubPages;
1659
    }
1660
1661
    /**
1662
     * Used by processItemStates() to evaluate if a menu item (identified by $key) is in a certain state.
1663
     *
1664
     * @param string $kind The item state to evaluate (SPC, IFSUB, ACT etc...)
1665
     * @param int $key Key pointing to menu item from ->menuArr
1666
     * @return bool Returns TRUE if state matches
1667
     * @see processItemStates()
1668
     */
1669
    protected function isItemState($kind, $key)
1670
    {
1671
        $natVal = false;
1672
        // If any value is set for ITEM_STATE the normal evaluation is discarded
1673
        if ($this->menuArr[$key]['ITEM_STATE'] ?? false) {
1674
            if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1675
                $natVal = true;
1676
            }
1677
        } else {
1678
            switch ($kind) {
1679
                case 'SPC':
1680
                    $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1681
                    break;
1682
                case 'IFSUB':
1683
                    $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1684
                    break;
1685
                case 'ACT':
1686
                    $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1687
                    break;
1688
                case 'ACTIFSUB':
1689
                    $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1690
                    break;
1691
                case 'CUR':
1692
                    $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
1693
                    break;
1694
                case 'CURIFSUB':
1695
                    $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1696
                    break;
1697
                case 'USR':
1698
                    $natVal = (bool)$this->menuArr[$key]['fe_group'];
1699
                    break;
1700
            }
1701
        }
1702
        return $natVal;
1703
    }
1704
1705
    /**
1706
     * Creates an access-key for a TMENU menu item based on the menu item titles first letter
1707
     *
1708
     * @param string $title Menu item title.
1709
     * @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
1710
     */
1711
    protected function accessKey($title)
1712
    {
1713
        $tsfe = $this->getTypoScriptFrontendController();
1714
        // The global array ACCESSKEY is used to globally control if letters are already used!!
1715
        $result = [];
1716
        $title = trim(strip_tags($title));
1717
        $titleLen = strlen($title);
1718
        for ($a = 0; $a < $titleLen; $a++) {
1719
            $key = strtoupper(substr($title, $a, 1));
1720
            if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
1721
                $tsfe->accessKey[$key] = true;
1722
                $result['code'] = ' accesskey="' . $key . '"';
1723
                $result['alt'] = ' (ALT+' . $key . ')';
1724
                $result['key'] = $key;
1725
                break;
1726
            }
1727
        }
1728
        return $result;
1729
    }
1730
1731
    /**
1732
     * Calls a user function for processing of internal data.
1733
     * Used for the properties "IProcFunc" and "itemArrayProcFunc"
1734
     *
1735
     * @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".
1736
     * @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
1737
     * @return mixed The processed $passVar
1738
     */
1739
    protected function userProcess($mConfKey, $passVar)
1740
    {
1741
        if ($this->mconf[$mConfKey]) {
1742
            $funcConf = (array)($this->mconf[$mConfKey . '.'] ?? []);
1743
            $funcConf['parentObj'] = $this;
1744
            $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
1745
        }
1746
        return $passVar;
1747
    }
1748
1749
    /**
1750
     * 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'])
1751
     */
1752
    protected function setATagParts()
1753
    {
1754
        $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
1755
        $params = $params !== '' ? ' ' . $params : '';
1756
        $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], true) . $params . '>';
1757
        $this->I['A2'] = '</a>';
1758
    }
1759
1760
    /**
1761
     * Returns the title for the navigation
1762
     *
1763
     * @param string $title The current page title
1764
     * @param string $nav_title The current value of the navigation title
1765
     * @return string Returns the navigation title if it is NOT blank, otherwise the page title.
1766
     */
1767
    protected function getPageTitle($title, $nav_title)
1768
    {
1769
        return trim($nav_title) !== '' ? $nav_title : $title;
1770
    }
1771
1772
    /**
1773
     * Return MPvar string for entry $key in ->menuArr
1774
     *
1775
     * @param int $key Pointer to element in ->menuArr
1776
     * @return string MP vars for element.
1777
     * @see link()
1778
     */
1779
    protected function getMPvar($key)
1780
    {
1781
        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
1782
            $localMP_array = $this->MP_array;
1783
            // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
1784
            if ($this->menuArr[$key]['_MP_PARAM'] ?? false) {
1785
                $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
1786
            }
1787
            return !empty($localMP_array) ? implode(',', $localMP_array) : '';
1788
        }
1789
        return '';
1790
    }
1791
1792
    /**
1793
     * Returns where clause part to exclude 'not in menu' pages
1794
     *
1795
     * @return string where clause part.
1796
     */
1797
    protected function getDoktypeExcludeWhere()
1798
    {
1799
        return !empty($this->excludedDoktypes) ? ' AND pages.doktype NOT IN (' . implode(',', $this->excludedDoktypes) . ')' : '';
1800
    }
1801
1802
    /**
1803
     * Returns an array of banned UIDs (from excludeUidList)
1804
     *
1805
     * @return array Array of banned UIDs
1806
     */
1807
    protected function getBannedUids()
1808
    {
1809
        $excludeUidList = (string)$this->parent_cObj->stdWrapValue('excludeUidList', $this->conf ?? []);
1810
        if (!trim($excludeUidList)) {
1811
            return [];
1812
        }
1813
1814
        $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page['uid'] ?? null, $excludeUidList);
1815
        return GeneralUtility::intExplode(',', $banUidList);
1816
    }
1817
1818
    /**
1819
     * Calls typolink to create menu item links.
1820
     *
1821
     * @param array $page Page record (uid points where to link to)
1822
     * @param string $oTarget Target frame/window
1823
     * @param string $addParams Parameters to add to URL
1824
     * @param int|string $typeOverride "type" value, empty string means "not set"
1825
     * @param int|null $overridePageId link to this page instead of the $page[uid] value
1826
     * @return array See linkData
1827
     */
1828
    protected function menuTypoLink($page, $oTarget, $addParams, $typeOverride, ?int $overridePageId = null)
1829
    {
1830
        $conf = [
1831
            'parameter' => $overridePageId ?? $page['uid']
1832
        ];
1833
        if (MathUtility::canBeInterpretedAsInteger($typeOverride)) {
1834
            $conf['parameter'] .= ',' . (int)$typeOverride;
1835
        }
1836
        if ($addParams) {
1837
            $conf['additionalParams'] = $addParams;
1838
        }
1839
1840
        // Ensure that the typolink gets an info which language was actually requested. The $page record could be the record
1841
        // from page translation language=1 as fallback but page translation language=2 was requested. Search for
1842
        // "_PAGES_OVERLAY_REQUESTEDLANGUAGE" for more details
1843
        if (isset($page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'])) {
1844
            $conf['language'] = $page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'];
1845
        }
1846
        if ($oTarget) {
1847
            $conf['target'] = $oTarget;
1848
        }
1849
        if ($page['sectionIndex_uid'] ?? false) {
1850
            $conf['section'] = $page['sectionIndex_uid'];
1851
        }
1852
        $conf['linkAccessRestrictedPages'] = !empty($this->mconf['showAccessRestrictedPages']);
1853
        $this->parent_cObj->typoLink('|', $conf);
1854
        $LD = $this->parent_cObj->lastTypoLinkLD;
1855
        $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
1856
        return $LD;
1857
    }
1858
1859
    /**
1860
     * Generates a list of content objects with sectionIndex enabled
1861
     * available on a specific page
1862
     *
1863
     * Used for menus with sectionIndex enabled
1864
     *
1865
     * @param string $altSortField Alternative sorting field
1866
     * @param int $pid The page id to search for sections
1867
     * @throws \UnexpectedValueException if the query to fetch the content elements unexpectedly fails
1868
     * @return array
1869
     */
1870
    protected function sectionIndex($altSortField, $pid = null)
1871
    {
1872
        $pid = (int)($pid ?: $this->id);
1873
        $basePageRow = $this->sys_page->getPage($pid);
1874
        if (!is_array($basePageRow)) {
0 ignored issues
show
introduced by
The condition is_array($basePageRow) is always true.
Loading history...
1875
            return [];
1876
        }
1877
        $useColPos = (int)$this->parent_cObj->stdWrapValue('useColPos', $this->mconf['sectionIndex.'] ?? [], 0);
1878
        $selectSetup = [
1879
            'pidInList' => $pid,
1880
            'orderBy' => $altSortField,
1881
            'languageField' => 'sys_language_uid',
1882
            'where' => ''
1883
        ];
1884
1885
        if ($useColPos >= 0) {
1886
            $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1887
                ->getConnectionForTable('tt_content')
1888
                ->getExpressionBuilder();
1889
            $selectSetup['where'] = $expressionBuilder->eq('colPos', $useColPos);
1890
        }
1891
1892
        if ($basePageRow['content_from_pid'] ?? false) {
1893
            // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
1894
            // the referenced page
1895
            $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
1896
        }
1897
        $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
1898
        if (!$statement) {
0 ignored issues
show
introduced by
$statement is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
1899
            $message = 'SectionIndex: Query to fetch the content elements failed!';
1900
            throw new \UnexpectedValueException($message, 1337334849);
1901
        }
1902
        $result = [];
1903
        while ($row = $statement->fetch()) {
1904
            $this->sys_page->versionOL('tt_content', $row);
1905
            if ($this->getCurrentLanguageAspect()->doOverlays() && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
1906
                $row = $this->sys_page->getRecordOverlay(
1907
                    'tt_content',
1908
                    $row,
1909
                    $basePageRow['_PAGES_OVERLAY_LANGUAGE'],
1910
                    $this->getCurrentLanguageAspect()->getOverlayType() === LanguageAspect::OVERLAYS_MIXED ? '1' : 'hideNonTranslated'
1911
                );
1912
            }
1913
            if ($this->mconf['sectionIndex.']['type'] !== 'all') {
1914
                $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
1915
                $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
1916
                $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
1917
                if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
1918
                    continue;
1919
                }
1920
            }
1921
            if (is_array($row)) {
1922
                $uid = $row['uid'] ?? null;
1923
                $result[$uid] = $basePageRow;
1924
                $result[$uid]['title'] = $row['header'];
1925
                $result[$uid]['nav_title'] = $row['header'];
1926
                // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
1927
                $result[$uid]['nav_hide'] = 0;
1928
                $result[$uid]['subtitle'] = $row['subheader'] ?? '';
1929
                $result[$uid]['starttime'] = $row['starttime'] ?? '';
1930
                $result[$uid]['endtime'] = $row['endtime'] ?? '';
1931
                $result[$uid]['fe_group'] = $row['fe_group'] ?? '';
1932
                $result[$uid]['media'] = $row['media'] ?? '';
1933
                $result[$uid]['header_layout'] = $row['header_layout'] ?? '';
1934
                $result[$uid]['bodytext'] = $row['bodytext'] ?? '';
1935
                $result[$uid]['image'] = $row['image'] ?? '';
1936
                $result[$uid]['sectionIndex_uid'] = $uid;
1937
            }
1938
        }
1939
1940
        return $result;
1941
    }
1942
1943
    /**
1944
     * Returns the sys_page object
1945
     *
1946
     * @return PageRepository
1947
     */
1948
    public function getSysPage()
1949
    {
1950
        return $this->sys_page;
1951
    }
1952
1953
    /**
1954
     * Returns the parent content object
1955
     *
1956
     * @return ContentObjectRenderer
1957
     */
1958
    public function getParentContentObject()
1959
    {
1960
        return $this->parent_cObj;
1961
    }
1962
1963
    /**
1964
     * @return TypoScriptFrontendController
1965
     */
1966
    protected function getTypoScriptFrontendController()
1967
    {
1968
        return $GLOBALS['TSFE'];
1969
    }
1970
1971
    protected function getCurrentLanguageAspect(): LanguageAspect
1972
    {
1973
        return GeneralUtility::makeInstance(Context::class)->getAspect('language');
1974
    }
1975
1976
    /**
1977
     * @return TimeTracker
1978
     */
1979
    protected function getTimeTracker()
1980
    {
1981
        return GeneralUtility::makeInstance(TimeTracker::class);
1982
    }
1983
1984
    /**
1985
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
1986
     */
1987
    protected function getCache()
1988
    {
1989
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
1990
    }
1991
1992
    /**
1993
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
1994
     */
1995
    protected function getRuntimeCache()
1996
    {
1997
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
1998
    }
1999
2000
    /**
2001
     * Returns the currently configured "site" if a site is configured (= resolved) in the current request.
2002
     *
2003
     * @return Site
2004
     */
2005
    protected function getCurrentSite(): Site
2006
    {
2007
        return $this->getTypoScriptFrontendController()->getSite();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getTypoScr...Controller()->getSite() returns the type TYPO3\CMS\Core\Site\Entity\SiteInterface which includes types incompatible with the type-hinted return TYPO3\CMS\Core\Site\Entity\Site.
Loading history...
2008
    }
2009
2010
    /**
2011
     * Set the parentMenuArr and key to provide the parentMenu information to the
2012
     * subMenu, special fur IProcFunc and itemArrayProcFunc user functions.
2013
     *
2014
     * @param array $menuArr
2015
     * @param int $menuItemKey
2016
     * @internal
2017
     */
2018
    public function setParentMenu(array $menuArr, $menuItemKey)
2019
    {
2020
        // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
2021
        if (is_array($menuArr)
2022
            && (is_int($menuItemKey) && $menuItemKey >= 0 && isset($menuArr[$menuItemKey]))
2023
        ) {
2024
            $this->parentMenuArr = $menuArr;
2025
            $this->parentMenuArrItemKey = $menuItemKey;
2026
        }
2027
    }
2028
2029
    /**
2030
     * Check if there is a valid parentMenuArr.
2031
     *
2032
     * @return bool
2033
     */
2034
    protected function hasParentMenuArr()
2035
    {
2036
        return
2037
            $this->menuNumber > 1
2038
            && is_array($this->parentMenuArr)
2039
            && !empty($this->parentMenuArr)
2040
        ;
2041
    }
2042
2043
    /**
2044
     * Check if we have a parentMenuArrItemKey
2045
     */
2046
    protected function hasParentMenuItemKey()
2047
    {
2048
        return null !== $this->parentMenuArrItemKey;
2049
    }
2050
2051
    /**
2052
     * Check if the the parentMenuItem exists
2053
     */
2054
    protected function hasParentMenuItem()
2055
    {
2056
        return
2057
            $this->hasParentMenuArr()
2058
            && $this->hasParentMenuItemKey()
2059
            && isset($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2060
        ;
2061
    }
2062
2063
    /**
2064
     * Get the parentMenuArr, if this is subMenu.
2065
     *
2066
     * @return array
2067
     */
2068
    public function getParentMenuArr()
2069
    {
2070
        return $this->hasParentMenuArr() ? $this->parentMenuArr : [];
2071
    }
2072
2073
    /**
2074
     * Get the parentMenuItem from the parentMenuArr, if this is a subMenu
2075
     *
2076
     * @return array|null
2077
     */
2078
    public function getParentMenuItem()
2079
    {
2080
        // check if we have a parentMenuItem and if it is an array
2081
        if ($this->hasParentMenuItem()
2082
            && is_array($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2083
        ) {
2084
            return $this->getParentMenuArr()[$this->parentMenuArrItemKey];
2085
        }
2086
2087
        return null;
2088
    }
2089
}
2090