AbstractMenuContentObject::processItemStates()   B
last analyzed

Complexity

Conditions 9
Paths 16

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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

254
                /** @scrutinizer ignore-type */ $this->parent_cObj->stdWrapValue('entryLevel', $this->conf ?? []),
Loading history...
255
                $this->tmpl->rootLine
256
            );
257
            // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
258
            // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
259
            if ($id) {
260
                $this->id = (int)$id;
261
            } else {
262
                // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
263
                $this->id = (int)($this->tmpl->rootLine[$this->entryLevel]['uid'] ?? 0);
264
265
                // Traverse rootline to build MP_array of pages BEFORE the entryLevel
266
                // (MP var for ->id is picked up in the next part of the code...)
267
                foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
268
                    // For overlaid mount points, set the variable right now:
269
                    if (($levelRec['_MP_PARAM'] ?? false) && ($levelRec['_MOUNT_OL'] ?? false)) {
270
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
271
                    }
272
273
                    // Break when entry level is reached:
274
                    if ($entryLevel >= $this->entryLevel) {
275
                        break;
276
                    }
277
278
                    // For normal mount points, set the variable for next level.
279
                    if (!empty($levelRec['_MP_PARAM']) && empty($levelRec['_MOUNT_OL'])) {
280
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
281
                    }
282
                }
283
            }
284
            // Return FALSE if no page ID was set (thus no menu of subpages can be made).
285
            if ($this->id <= 0) {
286
                return false;
287
            }
288
            // Check if page is a mount point, and if so set id and MP_array
289
            // (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...)
290
            $mount_info = $this->sys_page->getMountPointInfo($this->id);
291
            if (is_array($mount_info)) {
292
                $this->MP_array[] = $mount_info['MPvar'];
293
                $this->id = $mount_info['mount_pid'];
294
            }
295
            // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
296
            // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
297
            if ($this->rL_uidRegister === null) {
298
                $this->rL_uidRegister = [];
299
                $rl_MParray = [];
300
                foreach ($this->tmpl->rootLine as $v_rl) {
301
                    // For overlaid mount points, set the variable right now:
302
                    if (($v_rl['_MP_PARAM'] ?? false) && ($v_rl['_MOUNT_OL'] ?? false)) {
303
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
304
                    }
305
                    // Add to register:
306
                    $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
307
                        (
308
                            !empty($rl_MParray)
309
                            ? ':' . implode(',', $rl_MParray)
310
                            : ''
311
                        );
312
                    // For normal mount points, set the variable for next level.
313
                    if (($v_rl['_MP_PARAM'] ?? false) && !($v_rl['_MOUNT_OL'] ?? false)) {
314
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
315
                    }
316
                }
317
            }
318
            // Set $directoryLevel so the following evaluation of the nextActive will not return
319
            // an invalid value if .special=directory was set
320
            $directoryLevel = 0;
321
            if (($this->conf['special'] ?? '') === 'directory') {
322
                $value = $this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
323
                if ($value === '') {
324
                    $value = $tsfe->page['uid'];
325
                }
326
                $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
327
            }
328
            // 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
329
            // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
330
            $startLevel = $directoryLevel ?: $this->entryLevel;
331
            $currentLevel = $startLevel + $this->menuNumber;
332
            if (is_array($this->tmpl->rootLine[$currentLevel] ?? null)) {
333
                $nextMParray = $this->MP_array;
334
                if (empty($nextMParray) && !($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] ?? false) && $currentLevel > 0) {
335
                    // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
336
                    // otherwise automatic expansion will not work
337
                    $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
338
                    if (isset($parentRecord['_MP_PARAM'])) {
339
                        $nextMParray[] = $parentRecord['_MP_PARAM'];
340
                    }
341
                }
342
                // In overlay mode, add next level MPvars as well:
343
                if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] ?? false) {
344
                    $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
345
                }
346
                $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
347
                    (
348
                        !empty($nextMParray)
349
                        ? ':' . implode(',', $nextMParray)
350
                        : ''
351
                    );
352
            } else {
353
                $this->nextActive = '';
354
            }
355
            return true;
356
        }
357
        $this->getTimeTracker()->setTSlogMessage('ERROR in menu', LogLevel::ERROR);
358
        return false;
359
    }
360
361
    /**
362
     * Creates the menu in the internal variables, ready for output.
363
     * Basically this will read the page records needed and fill in the internal $this->menuArr
364
     * Based on a hash of this array and some other variables the $this->result variable will be
365
     * loaded either from cache OR by calling the generate() method of the class to create the menu for real.
366
     */
367
    public function makeMenu()
368
    {
369
        if (!$this->id) {
370
            return;
371
        }
372
373
        // Initializing showAccessRestrictedPages
374
        $SAVED_where_groupAccess = '';
375
        if ($this->mconf['showAccessRestrictedPages'] ?? false) {
376
            // SAVING where_groupAccess
377
            $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
378
            // Temporarily removing fe_group checking!
379
            $this->sys_page->where_groupAccess = '';
380
        }
381
382
        $menuItems = $this->prepareMenuItems();
383
384
        $c = 0;
385
        $c_b = 0;
386
387
        $minItems = (int)(($this->mconf['minItems'] ?? 0) ?: ($this->conf['minItems'] ?? 0));
388
        $maxItems = (int)(($this->mconf['maxItems'] ?? 0) ?: ($this->conf['maxItems'] ?? 0));
389
        $begin = $this->parent_cObj->calc(($this->mconf['begin'] ?? 0) ?: ($this->conf['begin'] ?? 0));
390
        $minItemsConf = $this->mconf['minItems.'] ?? $this->conf['minItems.'] ?? null;
391
        $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
392
        $maxItemsConf = $this->mconf['maxItems.'] ?? $this->conf['maxItems.'] ?? null;
393
        $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
394
        $beginConf = $this->mconf['begin.'] ?? $this->conf['begin.'] ?? null;
395
        $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
396
        $banUidArray = $this->getBannedUids();
397
        // Fill in the menuArr with elements that should go into the menu:
398
        $this->menuArr = [];
399
        foreach ($menuItems as $data) {
400
            $isSpacerPage = (int)($data['doktype'] ?? 0) === PageRepository::DOKTYPE_SPACER || ($data['ITEM_STATE'] ?? '') === 'SPC';
401
            // if item is a spacer, $spacer is set
402
            if ($this->filterMenuPages($data, $banUidArray, $isSpacerPage)) {
403
                $c_b++;
404
                // If the beginning item has been reached.
405
                if ($begin <= $c_b) {
406
                    $this->menuArr[$c] = $this->determineOriginalShortcutPage($data);
407
                    $this->menuArr[$c]['isSpacer'] = $isSpacerPage;
408
                    $c++;
409
                    if ($maxItems && $c >= $maxItems) {
410
                        break;
411
                    }
412
                }
413
            }
414
        }
415
        // Fill in fake items, if min-items is set.
416
        if ($minItems) {
417
            while ($c < $minItems) {
418
                $this->menuArr[$c] = [
419
                    'title' => '...',
420
                    'uid' => $this->getTypoScriptFrontendController()->id,
421
                ];
422
                $c++;
423
            }
424
        }
425
        //	Passing the menuArr through a user defined function:
426
        if ($this->mconf['itemArrayProcFunc'] ?? false) {
427
            $this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
428
        }
429
        // Setting number of menu items
430
        $this->getTypoScriptFrontendController()->register['count_menuItems'] = count($this->menuArr);
431
        $this->hash = md5(
432
            json_encode($this->menuArr) .
433
            json_encode($this->mconf) .
434
            json_encode($this->tmpl->rootLine) .
435
            json_encode($this->MP_array)
436
        );
437
        // Get the cache timeout:
438
        if ($this->conf['cache_period'] ?? false) {
439
            $cacheTimeout = $this->conf['cache_period'];
440
        } else {
441
            $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
442
        }
443
        $cache = $this->getCache();
444
        $cachedData = $cache->get($this->hash);
445
        if (!is_array($cachedData)) {
446
            $this->generate();
447
            $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
448
        } else {
449
            $this->result = $cachedData;
450
        }
451
        // End showAccessRestrictedPages
452
        if ($this->mconf['showAccessRestrictedPages'] ?? false) {
453
            // RESTORING where_groupAccess
454
            $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
455
        }
456
    }
457
458
    /**
459
     * Generates the the menu data.
460
     *
461
     * Subclasses should overwrite this method.
462
     */
463
    public function generate()
464
    {
465
    }
466
467
    /**
468
     * @return string The HTML for the menu
469
     */
470
    public function writeMenu()
471
    {
472
        return '';
473
    }
474
475
    /**
476
     * Gets an array of page rows and removes all, which are not accessible
477
     *
478
     * @param array $pages
479
     * @return array
480
     */
481
    protected function removeInaccessiblePages(array $pages)
482
    {
483
        $banned = $this->getBannedUids();
484
        $filteredPages = [];
485
        foreach ($pages as $aPage) {
486
            if ($this->filterMenuPages($aPage, $banned, (int)$aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
487
                $filteredPages[$aPage['uid']] = $aPage;
488
            }
489
        }
490
        return $filteredPages;
491
    }
492
493
    /**
494
     * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
495
     *
496
     * @return array
497
     */
498
    protected function prepareMenuItems()
499
    {
500
        $menuItems = [];
501
        $alternativeSortingField = trim($this->mconf['alternativeSortingField'] ?? '') ?: 'sorting';
502
503
        // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
504
        $additionalWhere = $this->parent_cObj->stdWrapValue('additionalWhere', $this->mconf ?? []);
505
        $additionalWhere .= $this->getDoktypeExcludeWhere();
506
507
        // ... only for the FIRST level of a HMENU
508
        if ($this->menuNumber == 1 && ($this->conf['special'] ?? false)) {
509
            $value = (string)$this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
510
            switch ($this->conf['special']) {
511
                case 'userfunction':
512
                    $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
513
                    break;
514
                case 'language':
515
                    $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
516
                    break;
517
                case 'directory':
518
                    $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
519
                    break;
520
                case 'list':
521
                    $menuItems = $this->prepareMenuItemsForListMenu($value);
522
                    break;
523
                case 'updated':
524
                    $menuItems = $this->prepareMenuItemsForUpdatedMenu(
525
                        $value,
526
                        $this->mconf['alternativeSortingField'] ?? ''
527
                    );
528
                    break;
529
                case 'keywords':
530
                    $menuItems = $this->prepareMenuItemsForKeywordsMenu(
531
                        $value,
532
                        $this->mconf['alternativeSortingField'] ?? ''
533
                    );
534
                    break;
535
                case 'categories':
536
                    /** @var CategoryMenuUtility $categoryMenuUtility */
537
                    $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
538
                    $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
539
                    break;
540
                case 'rootline':
541
                    $menuItems = $this->prepareMenuItemsForRootlineMenu();
542
                    break;
543
                case 'browse':
544
                    $menuItems = $this->prepareMenuItemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
545
                    break;
546
            }
547
            if ($this->mconf['sectionIndex'] ?? false) {
548
                $sectionIndexes = [];
549
                foreach ($menuItems as $page) {
550
                    $sectionIndexes = $sectionIndexes + $this->sectionIndex($alternativeSortingField, $page['uid']);
551
                }
552
                $menuItems = $sectionIndexes;
553
            }
554
        } elseif ($this->alternativeMenuTempArray !== []) {
555
            // Setting $menuItems array if not level 1.
556
            $menuItems = $this->alternativeMenuTempArray;
557
        } elseif ($this->mconf['sectionIndex'] ?? false) {
558
            $menuItems = $this->sectionIndex($alternativeSortingField);
559
        } else {
560
            // Default: Gets a hierarchical menu based on subpages of $this->id
561
            $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
562
        }
563
        return $menuItems;
564
    }
565
566
    /**
567
     * Fetches all menuitems if special = userfunction is set
568
     *
569
     * @param string $specialValue The value from special.value
570
     * @param string $sortingField The sorting field
571
     * @return array
572
     */
573
    protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
574
    {
575
        $menuItems = $this->parent_cObj->callUserFunction(
576
            $this->conf['special.']['userFunc'],
577
            array_merge($this->conf['special.'], ['value' => $specialValue, '_altSortField' => $sortingField]),
578
            ''
579
        );
580
        return is_array($menuItems) ? $menuItems : [];
581
    }
582
583
    /**
584
     * Fetches all menuitems if special = language is set
585
     *
586
     * @param string $specialValue The value from special.value
587
     * @return array
588
     */
589
    protected function prepareMenuItemsForLanguageMenu($specialValue)
590
    {
591
        $menuItems = [];
592
        // Getting current page record NOT overlaid by any translation:
593
        $tsfe = $this->getTypoScriptFrontendController();
594
        $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->page['uid']);
595
596
        if ($specialValue === 'auto') {
597
            $site = $this->getCurrentSite();
598
            $languages = $site->getLanguages();
599
            $languageItems = array_keys($languages);
600
        } else {
601
            $languageItems = GeneralUtility::intExplode(',', $specialValue);
602
        }
603
604
        $tsfe->register['languages_HMENU'] = implode(',', $languageItems);
605
606
        $currentLanguageId = $this->getCurrentLanguageAspect()->getId();
607
608
        foreach ($languageItems as $sUid) {
609
            // Find overlay record:
610
            if ($sUid) {
611
                $lRecs = $this->sys_page->getPageOverlay($tsfe->page['uid'], $sUid);
612
            } else {
613
                $lRecs = [];
614
            }
615
            // Checking if the "disabled" state should be set.
616
            $pageTranslationVisibility = new PageTranslationVisibility((int)($tsfe->page['l18n_cfg'] ?? 0));
617
            if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() && $sUid &&
618
                empty($lRecs) || $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() &&
619
                (!$sUid || empty($lRecs)) ||
620
                !($this->conf['special.']['normalWhenNoLanguage'] ?? false) && $sUid && empty($lRecs)
621
            ) {
622
                $iState = $currentLanguageId === $sUid ? 'USERDEF2' : 'USERDEF1';
623
            } else {
624
                $iState = $currentLanguageId === $sUid ? 'ACT' : 'NO';
625
            }
626
            $getVars = '';
627
            if ($this->conf['addQueryString'] ?? false) {
628
                $getVars = $this->parent_cObj->getQueryArguments($this->conf['addQueryString.']);
629
            }
630
            // Adding menu item:
631
            $menuItems[] = array_merge(
632
                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

632
                array_merge(/** @scrutinizer ignore-type */ $currentPageWithNoOverlay, $lRecs),
Loading history...
633
                [
634
                    '_PAGES_OVERLAY_REQUESTEDLANGUAGE' => $sUid,
635
                    'ITEM_STATE' => $iState,
636
                    '_ADD_GETVARS' => $getVars,
637
                    '_SAFE' => true,
638
                ]
639
            );
640
        }
641
        return $menuItems;
642
    }
643
644
    /**
645
     * Fetches all menuitems if special = directory is set
646
     *
647
     * @param string $specialValue The value from special.value
648
     * @param string $sortingField The sorting field
649
     * @return array
650
     */
651
    protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
652
    {
653
        $tsfe = $this->getTypoScriptFrontendController();
654
        $menuItems = [];
655
        if ($specialValue == '') {
656
            $specialValue = $tsfe->page['uid'];
657
        }
658
        $items = GeneralUtility::intExplode(',', $specialValue);
659
        $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
660
        foreach ($items as $id) {
661
            $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($id);
662
            // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
663
            $mount_info = $this->sys_page->getMountPointInfo($id);
664
            if (is_array($mount_info)) {
665
                if ($mount_info['overlay']) {
666
                    // Overlays should already have their full MPvars calculated:
667
                    $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid']);
668
                    $MP = $MP ?: $mount_info['MPvar'];
669
                } else {
670
                    $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
671
                }
672
                $id = $mount_info['mount_pid'];
673
            }
674
            // Get sub-pages:
675
            $statement = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
676
            while ($row = $statement->fetchAssociative()) {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::fetchAssociative() has been deprecated: Use Result::fetchAssociative() instead ( Ignorable by Annotation )

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

676
            while ($row = /** @scrutinizer ignore-deprecated */ $statement->fetchAssociative()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
677
                // When the site language configuration is in "free" mode, then the page without overlay is fetched
678
                // (which is kind-of strange for pages, but this is what exec_getQuery() is doing)
679
                // this means, that $row is a translated page, but hasn't been overlaid. For this reason, we fetch
680
                // the default translation page again, (which does a ->getPageOverlay() again - doing this on a
681
                // translated page would result in no record at all)
682
                if ($row['l10n_parent'] > 0 && !isset($row['_PAGES_OVERLAY'])) {
683
                    $row = $this->sys_page->getPage($row['l10n_parent'], true);
684
                }
685
                $tsfe->sys_page->versionOL('pages', $row, true);
686
                if (!empty($row)) {
687
                    // Keep mount point?
688
                    $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
689
                    // There is a valid mount point.
690
                    if (is_array($mount_info) && $mount_info['overlay']) {
691
                        // Using "getPage" is OK since we need the check for enableFields
692
                        // AND for type 2 of mount pids we DO require a doktype < 200!
693
                        $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
694
                        if (!empty($mp_row)) {
695
                            $row = $mp_row;
696
                            $row['_MP_PARAM'] = $mount_info['MPvar'];
697
                        } else {
698
                            // If the mount point could not be fetched with respect
699
                            // to enableFields, unset the row so it does not become a part of the menu!
700
                            unset($row);
701
                        }
702
                    }
703
                    // Add external MP params, then the row:
704
                    if (!empty($row)) {
705
                        if ($MP) {
706
                            $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
707
                        }
708
                        $menuItems[] = $this->sys_page->getPageOverlay($row);
709
                    }
710
                }
711
            }
712
        }
713
714
        return $menuItems;
715
    }
716
717
    /**
718
     * Fetches all menuitems if special = list is set
719
     *
720
     * @param string $specialValue The value from special.value
721
     * @return array
722
     */
723
    protected function prepareMenuItemsForListMenu($specialValue)
724
    {
725
        $menuItems = [];
726
        if ($specialValue == '') {
727
            $specialValue = $this->id;
728
        }
729
        $pageIds = GeneralUtility::intExplode(',', (string)$specialValue);
730
        $disableGroupAccessCheck = !empty($this->mconf['showAccessRestrictedPages']);
731
        $pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
732
        foreach ($pageIds as $pageId) {
733
            $row = $this->sys_page->getPage($pageId, $disableGroupAccessCheck);
734
            if (!is_array($row)) {
735
                continue;
736
            }
737
            $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($pageId);
738
            // Keep mount point?
739
            $mount_info = $this->sys_page->getMountPointInfo($pageId, $row);
740
            // $pageId is a valid mount point
741
            if (is_array($mount_info) && $mount_info['overlay']) {
742
                $mountedPageId = (int)$mount_info['mount_pid'];
743
                // Using "getPage" is OK since we need the check for enableFields
744
                // AND for type 2 of mount pids we DO require a doktype < 200!
745
                $mountedPageRow = $this->sys_page->getPage($mountedPageId, $disableGroupAccessCheck);
746
                if (empty($mountedPageRow)) {
747
                    // If the mount point could not be fetched with respect to
748
                    // enableFields, the page should not become a part of the menu!
749
                    continue;
750
                }
751
                $row = $mountedPageRow;
752
                $row['_MP_PARAM'] = $mount_info['MPvar'];
753
                // Overlays should already have their full MPvars calculated, that's why we unset the
754
                // existing $row['_MP_PARAM'], as the full $MP will be added again below
755
                $MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($mountedPageId);
756
                if ($MP) {
757
                    unset($row['_MP_PARAM']);
758
                }
759
            }
760
            if ($MP) {
761
                $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
762
            }
763
            $menuItems[] = $row;
764
        }
765
        return $menuItems;
766
    }
767
768
    /**
769
     * Fetches all menuitems if special = updated is set
770
     *
771
     * @param string $specialValue The value from special.value
772
     * @param string $sortingField The sorting field
773
     * @return array
774
     */
775
    protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
776
    {
777
        $tsfe = $this->getTypoScriptFrontendController();
778
        $menuItems = [];
779
        if ($specialValue == '') {
780
            $specialValue = $tsfe->page['uid'];
781
        }
782
        $items = GeneralUtility::intExplode(',', $specialValue);
783
        if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'] ?? null)) {
784
            $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
785
        } else {
786
            $depth = 20;
787
        }
788
        // Max number of items
789
        $limit = MathUtility::forceIntegerInRange(($this->conf['special.']['limit'] ?? 0), 0, 100);
790
        $maxAge = (int)($this->parent_cObj->calc($this->conf['special.']['maxAge'] ?? 0));
791
        if (!$limit) {
792
            $limit = 10;
793
        }
794
        // 'auto', 'manual', 'tstamp'
795
        $mode = $this->conf['special.']['mode'] ?? '';
796
        // Get id's
797
        $beginAtLevel = MathUtility::forceIntegerInRange(($this->conf['special.']['beginAtLevel'] ?? 0), 0, 100);
798
        $id_list_arr = [];
799
        foreach ($items as $id) {
800
            // Exclude the current ID if beginAtLevel is > 0
801
            if ($beginAtLevel > 0) {
802
                $id_list_arr[] = $this->parent_cObj->getTreeList($id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
803
            } else {
804
                $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $beginAtLevel, $beginAtLevel - 1);
805
            }
806
        }
807
        $id_list = implode(',', $id_list_arr);
808
        // Get sortField (mode)
809
        $sortField = $this->getMode($mode);
810
811
        $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
812
        if ($this->conf['special.']['excludeNoSearchPages'] ?? false) {
813
            $extraWhere .= ' AND pages.no_search=0';
814
        }
815
        if ($maxAge > 0) {
816
            $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
817
        }
818
        $statement = $this->parent_cObj->exec_getQuery('pages', [
819
            'pidInList' => '0',
820
            'uidInList' => $id_list,
821
            'where' => $sortField . '>=0' . $extraWhere,
822
            'orderBy' => $sortingField ?: $sortField . ' DESC',
823
            'max' => $limit,
824
        ]);
825
        while ($row = $statement->fetchAssociative()) {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::fetchAssociative() has been deprecated: Use Result::fetchAssociative() instead ( Ignorable by Annotation )

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

825
        while ($row = /** @scrutinizer ignore-deprecated */ $statement->fetchAssociative()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
826
            // When the site language configuration is in "free" mode, then the page without overlay is fetched
827
            // (which is kind-of strange for pages, but this is what exec_getQuery() is doing)
828
            // this means, that $row is a translated page, but hasn't been overlaid. For this reason, we fetch
829
            // the default translation page again, (which does a ->getPageOverlay() again - doing this on a
830
            // translated page would result in no record at all)
831
            if ($row['l10n_parent'] > 0 && !isset($row['_PAGES_OVERLAY'])) {
832
                $row = $this->sys_page->getPage($row['l10n_parent'], true);
833
            }
834
            $tsfe->sys_page->versionOL('pages', $row, true);
835
            if (is_array($row)) {
836
                $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
837
            }
838
        }
839
840
        return $menuItems;
841
    }
842
843
    /**
844
     * Fetches all menuitems if special = keywords is set
845
     *
846
     * @param string $specialValue The value from special.value
847
     * @param string $sortingField The sorting field
848
     * @return array
849
     */
850
    protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
851
    {
852
        $tsfe = $this->getTypoScriptFrontendController();
853
        $menuItems = [];
854
        [$specialValue] = GeneralUtility::intExplode(',', $specialValue);
855
        if (!$specialValue) {
856
            $specialValue = $tsfe->page['uid'];
857
        }
858
        if (($this->conf['special.']['setKeywords'] ?? false) || ($this->conf['special.']['setKeywords.'] ?? false)) {
859
            $kw = (string)$this->parent_cObj->stdWrapValue('setKeywords', $this->conf['special.'] ?? []);
860
        } else {
861
            // The page record of the 'value'.
862
            $value_rec = $this->sys_page->getPage($specialValue);
863
            $kfieldSrc = ($this->conf['special.']['keywordsField.']['sourceField'] ?? false) ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
864
            // keywords.
865
            $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
866
        }
867
        // *'auto', 'manual', 'tstamp'
868
        $mode = $this->conf['special.']['mode'] ?? '';
869
        $sortField = $this->getMode($mode);
870
        // Depth, limit, extra where
871
        if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'] ?? null)) {
872
            $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
873
        } else {
874
            $depth = 20;
875
        }
876
        // Max number of items
877
        $limit = MathUtility::forceIntegerInRange(($this->conf['special.']['limit'] ?? 0), 0, 100);
878
        // Start point
879
        $eLevel = $this->parent_cObj->getKey(
880
            $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

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

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

1847
        while ($row = /** @scrutinizer ignore-deprecated */ $statement->fetchAssociative()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1848
            $this->sys_page->versionOL('tt_content', $row);
1849
            if ($this->getCurrentLanguageAspect()->doOverlays() && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
1850
                $row = $this->sys_page->getRecordOverlay(
1851
                    'tt_content',
1852
                    $row,
1853
                    $basePageRow['_PAGES_OVERLAY_LANGUAGE'],
1854
                    $this->getCurrentLanguageAspect()->getOverlayType() === LanguageAspect::OVERLAYS_MIXED ? '1' : 'hideNonTranslated'
1855
                );
1856
            }
1857
            if ($this->mconf['sectionIndex.']['type'] !== 'all') {
1858
                $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
1859
                $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
1860
                $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
1861
                if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
1862
                    continue;
1863
                }
1864
            }
1865
            if (is_array($row)) {
1866
                $uid = $row['uid'] ?? null;
1867
                $result[$uid] = $basePageRow;
1868
                $result[$uid]['title'] = $row['header'];
1869
                $result[$uid]['nav_title'] = $row['header'];
1870
                // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
1871
                $result[$uid]['nav_hide'] = 0;
1872
                $result[$uid]['subtitle'] = $row['subheader'] ?? '';
1873
                $result[$uid]['starttime'] = $row['starttime'] ?? '';
1874
                $result[$uid]['endtime'] = $row['endtime'] ?? '';
1875
                $result[$uid]['fe_group'] = $row['fe_group'] ?? '';
1876
                $result[$uid]['media'] = $row['media'] ?? '';
1877
                $result[$uid]['header_layout'] = $row['header_layout'] ?? '';
1878
                $result[$uid]['bodytext'] = $row['bodytext'] ?? '';
1879
                $result[$uid]['image'] = $row['image'] ?? '';
1880
                $result[$uid]['sectionIndex_uid'] = $uid;
1881
            }
1882
        }
1883
1884
        return $result;
1885
    }
1886
1887
    /**
1888
     * Returns the sys_page object
1889
     *
1890
     * @return PageRepository
1891
     */
1892
    public function getSysPage()
1893
    {
1894
        return $this->sys_page;
1895
    }
1896
1897
    /**
1898
     * Returns the parent content object
1899
     *
1900
     * @return ContentObjectRenderer
1901
     */
1902
    public function getParentContentObject()
1903
    {
1904
        return $this->parent_cObj;
1905
    }
1906
1907
    /**
1908
     * @return TypoScriptFrontendController
1909
     */
1910
    protected function getTypoScriptFrontendController()
1911
    {
1912
        return $GLOBALS['TSFE'];
1913
    }
1914
1915
    protected function getCurrentLanguageAspect(): LanguageAspect
1916
    {
1917
        return GeneralUtility::makeInstance(Context::class)->getAspect('language');
1918
    }
1919
1920
    /**
1921
     * @return TimeTracker
1922
     */
1923
    protected function getTimeTracker()
1924
    {
1925
        return GeneralUtility::makeInstance(TimeTracker::class);
1926
    }
1927
1928
    /**
1929
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
1930
     */
1931
    protected function getCache()
1932
    {
1933
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
1934
    }
1935
1936
    /**
1937
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
1938
     */
1939
    protected function getRuntimeCache()
1940
    {
1941
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
1942
    }
1943
1944
    /**
1945
     * Returns the currently configured "site" if a site is configured (= resolved) in the current request.
1946
     *
1947
     * @return Site
1948
     */
1949
    protected function getCurrentSite(): Site
1950
    {
1951
        return $this->getTypoScriptFrontendController()->getSite();
1952
    }
1953
1954
    /**
1955
     * Set the parentMenuArr and key to provide the parentMenu information to the
1956
     * subMenu, special fur IProcFunc and itemArrayProcFunc user functions.
1957
     *
1958
     * @param array $menuArr
1959
     * @param int $menuItemKey
1960
     * @internal
1961
     */
1962
    public function setParentMenu(array $menuArr, $menuItemKey)
1963
    {
1964
        // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
1965
        if (is_array($menuArr)
1966
            && (is_int($menuItemKey) && $menuItemKey >= 0 && isset($menuArr[$menuItemKey]))
1967
        ) {
1968
            $this->parentMenuArr = $menuArr;
1969
            $this->parentMenuArrItemKey = $menuItemKey;
1970
        }
1971
    }
1972
1973
    /**
1974
     * Check if there is a valid parentMenuArr.
1975
     *
1976
     * @return bool
1977
     */
1978
    protected function hasParentMenuArr()
1979
    {
1980
        return
1981
            $this->menuNumber > 1
1982
            && is_array($this->parentMenuArr)
1983
            && !empty($this->parentMenuArr)
1984
        ;
1985
    }
1986
1987
    /**
1988
     * Check if we have a parentMenuArrItemKey
1989
     */
1990
    protected function hasParentMenuItemKey()
1991
    {
1992
        return null !== $this->parentMenuArrItemKey;
1993
    }
1994
1995
    /**
1996
     * Check if the the parentMenuItem exists
1997
     */
1998
    protected function hasParentMenuItem()
1999
    {
2000
        return
2001
            $this->hasParentMenuArr()
2002
            && $this->hasParentMenuItemKey()
2003
            && isset($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2004
        ;
2005
    }
2006
2007
    /**
2008
     * Get the parentMenuArr, if this is subMenu.
2009
     *
2010
     * @return array
2011
     */
2012
    public function getParentMenuArr()
2013
    {
2014
        return $this->hasParentMenuArr() ? $this->parentMenuArr : [];
2015
    }
2016
2017
    /**
2018
     * Get the parentMenuItem from the parentMenuArr, if this is a subMenu
2019
     *
2020
     * @return array|null
2021
     */
2022
    public function getParentMenuItem()
2023
    {
2024
        // check if we have a parentMenuItem and if it is an array
2025
        if ($this->hasParentMenuItem()
2026
            && is_array($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2027
        ) {
2028
            return $this->getParentMenuArr()[$this->parentMenuArrItemKey];
2029
        }
2030
2031
        return null;
2032
    }
2033
2034
    /**
2035
     * @param string $mode
2036
     * @return string
2037
     */
2038
    private function getMode(string $mode = ''): string
2039
    {
2040
        switch ($mode) {
2041
            case 'starttime':
2042
                $sortField = 'starttime';
2043
                break;
2044
            case 'lastUpdated':
2045
            case 'manual':
2046
                $sortField = 'lastUpdated';
2047
                break;
2048
            case 'tstamp':
2049
                $sortField = 'tstamp';
2050
                break;
2051
            case 'crdate':
2052
                $sortField = 'crdate';
2053
                break;
2054
            default:
2055
                $sortField = 'SYS_LASTCHANGED';
2056
        }
2057
2058
        return $sortField;
2059
    }
2060
}
2061