AbstractMenuContentObject::hasParentMenuArr()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
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'] ?? false) && $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'] ?? false) {
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'] ?? 0) === 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'] ?? false)) {
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'] ?? false) {
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'] ?? null)) {
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), 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), 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
        $sortField = $this->getMode($mode);
818
819
        $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
820
        if ($this->conf['special.']['excludeNoSearchPages']) {
821
            $extraWhere .= ' AND pages.no_search=0';
822
        }
823
        if ($maxAge > 0) {
824
            $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
825
        }
826
        $statement = $this->parent_cObj->exec_getQuery('pages', [
827
            'pidInList' => '0',
828
            'uidInList' => $id_list,
829
            'where' => $sortField . '>=0' . $extraWhere,
830
            'orderBy' => $sortingField ?: $sortField . ' DESC',
831
            'max' => $limit
832
        ]);
833
        while ($row = $statement->fetch()) {
834
            // When the site language configuration is in "free" mode, then the page without overlay is fetched
835
            // (which is kind-of strange for pages, but this is what exec_getQuery() is doing)
836
            // this means, that $row is a translated page, but hasn't been overlaid. For this reason, we fetch
837
            // the default translation page again, (which does a ->getPageOverlay() again - doing this on a
838
            // translated page would result in no record at all)
839
            if ($row['l10n_parent'] > 0 && !isset($row['_PAGES_OVERLAY'])) {
840
                $row = $this->sys_page->getPage($row['l10n_parent'], true);
841
            }
842
            $tsfe->sys_page->versionOL('pages', $row, true);
843
            if (is_array($row)) {
844
                $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
845
            }
846
        }
847
848
        return $menuItems;
849
    }
850
851
    /**
852
     * Fetches all menuitems if special = keywords is set
853
     *
854
     * @param string $specialValue The value from special.value
855
     * @param string $sortingField The sorting field
856
     * @return array
857
     */
858
    protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
859
    {
860
        $tsfe = $this->getTypoScriptFrontendController();
861
        $menuItems = [];
862
        [$specialValue] = GeneralUtility::intExplode(',', $specialValue);
863
        if (!$specialValue) {
864
            $specialValue = $tsfe->page['uid'];
865
        }
866
        if (($this->conf['special.']['setKeywords'] ?? false) || ($this->conf['special.']['setKeywords.'] ?? false)) {
867
            $kw = (string)$this->parent_cObj->stdWrapValue('setKeywords', $this->conf['special.'] ?? []);
868
        } else {
869
            // The page record of the 'value'.
870
            $value_rec = $this->sys_page->getPage($specialValue);
871
            $kfieldSrc = ($this->conf['special.']['keywordsField.']['sourceField'] ?? false) ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
872
            // keywords.
873
            $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
874
        }
875
        // *'auto', 'manual', 'tstamp'
876
        $mode = $this->conf['special.']['mode'] ?? '';
877
        $sortField = $this->getMode($mode);
878
        // Depth, limit, extra where
879
        if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'] ?? null)) {
880
            $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
881
        } else {
882
            $depth = 20;
883
        }
884
        // Max number of items
885
        $limit = MathUtility::forceIntegerInRange(($this->conf['special.']['limit'] ?? 0), 0, 100);
886
        // Start point
887
        $eLevel = $this->parent_cObj->getKey(
888
            $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

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