Completed
Push — master ( 8e078a...edce3c )
by
unknown
15:28
created

AbstractMenuContentObject::hasParentMenuItem()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

241
            $this->conf['includeNotInMenu'] = $this->parent_cObj->stdWrapValue('includeNotInMenu', $this->conf, /** @scrutinizer ignore-type */ false);
Loading history...
242
            // exclude doktypes that should not be shown in menu (e.g. backend user section)
243
            if ($this->conf['excludeDoktypes']) {
244
                $this->excludedDoktypes = GeneralUtility::intExplode(',', $this->conf['excludeDoktypes']);
245
            }
246
            // EntryLevel
247
            $this->entryLevel = $this->parent_cObj->getKey(
248
                $this->parent_cObj->stdWrapValue('entryLevel', $this->conf),
0 ignored issues
show
Bug introduced by
It seems like $this->parent_cObj->stdW...tryLevel', $this->conf) can also be of type string; however, parameter $key of TYPO3\CMS\Frontend\Conte...bjectRenderer::getKey() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

248
                /** @scrutinizer ignore-type */ $this->parent_cObj->stdWrapValue('entryLevel', $this->conf),
Loading history...
249
                $this->tmpl->rootLine
250
            );
251
            // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
252
            // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
253
            if ($id) {
254
                $this->id = (int)$id;
255
            } else {
256
                // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
257
                $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
258
                // Traverse rootline to build MP_array of pages BEFORE the entryLevel
259
                // (MP var for ->id is picked up in the next part of the code...)
260
                foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
261
                    // For overlaid mount points, set the variable right now:
262
                    if ($levelRec['_MP_PARAM'] && $levelRec['_MOUNT_OL']) {
263
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
264
                    }
265
                    // Break when entry level is reached:
266
                    if ($entryLevel >= $this->entryLevel) {
267
                        break;
268
                    }
269
                    // For normal mount points, set the variable for next level.
270
                    if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
271
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
272
                    }
273
                }
274
            }
275
            // Return FALSE if no page ID was set (thus no menu of subpages can be made).
276
            if ($this->id <= 0) {
277
                return false;
278
            }
279
            // Check if page is a mount point, and if so set id and MP_array
280
            // (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...)
281
            $mount_info = $this->sys_page->getMountPointInfo($this->id);
282
            if (is_array($mount_info)) {
283
                $this->MP_array[] = $mount_info['MPvar'];
284
                $this->id = $mount_info['mount_pid'];
285
            }
286
            // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
287
            // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
288
            if ($this->rL_uidRegister === null) {
289
                $this->rL_uidRegister = [];
290
                $rl_MParray = [];
291
                foreach ($this->tmpl->rootLine as $v_rl) {
292
                    // For overlaid mount points, set the variable right now:
293
                    if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
294
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
295
                    }
296
                    // Add to register:
297
                    $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
298
                        (
299
                            !empty($rl_MParray)
300
                            ? ':' . implode(',', $rl_MParray)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
301
                            : ''
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
302
                        );
303
                    // For normal mount points, set the variable for next level.
304
                    if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
305
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
306
                    }
307
                }
308
            }
309
            // Set $directoryLevel so the following evaluation of the nextActive will not return
310
            // an invalid value if .special=directory was set
311
            $directoryLevel = 0;
312
            if ($this->conf['special'] === 'directory') {
313
                $value = $this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
314
                if ($value === '') {
315
                    $value = $tsfe->page['uid'];
316
                }
317
                $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
318
            }
319
            // 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
320
            // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
321
            $startLevel = $directoryLevel ?: $this->entryLevel;
322
            $currentLevel = $startLevel + $this->menuNumber;
323
            if (is_array($this->tmpl->rootLine[$currentLevel])) {
324
                $nextMParray = $this->MP_array;
325
                if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
326
                    // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
327
                    // otherwise automatic expansion will not work
328
                    $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
329
                    if (isset($parentRecord['_MP_PARAM'])) {
330
                        $nextMParray[] = $parentRecord['_MP_PARAM'];
331
                    }
332
                }
333
                // In overlay mode, add next level MPvars as well:
334
                if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
335
                    $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
336
                }
337
                $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
338
                    (
339
                        !empty($nextMParray)
340
                        ? ':' . implode(',', $nextMParray)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
341
                        : ''
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
342
                    );
343
            } else {
344
                $this->nextActive = '';
345
            }
346
            return true;
347
        }
348
        $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
349
        return false;
350
    }
351
352
    /**
353
     * Creates the menu in the internal variables, ready for output.
354
     * Basically this will read the page records needed and fill in the internal $this->menuArr
355
     * Based on a hash of this array and some other variables the $this->result variable will be
356
     * loaded either from cache OR by calling the generate() method of the class to create the menu for real.
357
     */
358
    public function makeMenu()
359
    {
360
        if (!$this->id) {
361
            return;
362
        }
363
364
        // Initializing showAccessRestrictedPages
365
        $SAVED_where_groupAccess = '';
366
        if ($this->mconf['showAccessRestrictedPages']) {
367
            // SAVING where_groupAccess
368
            $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
369
            // Temporarily removing fe_group checking!
370
            $this->sys_page->where_groupAccess = '';
371
        }
372
373
        $menuItems = $this->prepareMenuItems();
374
375
        $c = 0;
376
        $c_b = 0;
377
        $minItems = (int)($this->mconf['minItems'] ?: $this->conf['minItems']);
378
        $maxItems = (int)($this->mconf['maxItems'] ?: $this->conf['maxItems']);
379
        $begin = $this->parent_cObj->calc($this->mconf['begin'] ?: $this->conf['begin']);
380
        $minItemsConf = $this->mconf['minItems.'] ?? $this->conf['minItems.'] ?? null;
381
        $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
382
        $maxItemsConf = $this->mconf['maxItems.'] ?? $this->conf['maxItems.'] ?? null;
383
        $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
384
        $beginConf = $this->mconf['begin.'] ?? $this->conf['begin.'] ?? null;
385
        $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
386
        $banUidArray = $this->getBannedUids();
387
        // Fill in the menuArr with elements that should go into the menu:
388
        $this->menuArr = [];
389
        foreach ($menuItems as $data) {
390
            $isSpacerPage = (int)$data['doktype'] === PageRepository::DOKTYPE_SPACER || $data['ITEM_STATE'] === 'SPC';
391
            // if item is a spacer, $spacer is set
392
            if ($this->filterMenuPages($data, $banUidArray, $isSpacerPage)) {
393
                $c_b++;
394
                // If the beginning item has been reached.
395
                if ($begin <= $c_b) {
396
                    $this->menuArr[$c] = $this->determineOriginalShortcutPage($data);
397
                    $this->menuArr[$c]['isSpacer'] = $isSpacerPage;
398
                    $c++;
399
                    if ($maxItems && $c >= $maxItems) {
400
                        break;
401
                    }
402
                }
403
            }
404
        }
405
        // Fill in fake items, if min-items is set.
406
        if ($minItems) {
407
            while ($c < $minItems) {
408
                $this->menuArr[$c] = [
409
                    'title' => '...',
410
                    'uid' => $this->getTypoScriptFrontendController()->id
411
                ];
412
                $c++;
413
            }
414
        }
415
        //	Passing the menuArr through a user defined function:
416
        if ($this->mconf['itemArrayProcFunc']) {
417
            $this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->userProcess('item...cFunc', $this->menuArr) can also be of type string. However, the property $menuArr is declared as type array<mixed,array>. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

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

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

420
        $this->getTypoScriptFrontendController()->register['count_menuItems'] = count(/** @scrutinizer ignore-type */ $this->menuArr);
Loading history...
421
        $this->hash = md5(
422
            json_encode($this->menuArr) .
423
            json_encode($this->mconf) .
424
            json_encode($this->tmpl->rootLine) .
425
            json_encode($this->MP_array)
426
        );
427
        // Get the cache timeout:
428
        if ($this->conf['cache_period']) {
429
            $cacheTimeout = $this->conf['cache_period'];
430
        } else {
431
            $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
432
        }
433
        $cache = $this->getCache();
434
        $cachedData = $cache->get($this->hash);
435
        if (!is_array($cachedData)) {
436
            $this->generate();
437
            $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
438
        } else {
439
            $this->result = $cachedData;
440
        }
441
        // End showAccessRestrictedPages
442
        if ($this->mconf['showAccessRestrictedPages']) {
443
            // RESTORING where_groupAccess
444
            $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
445
        }
446
    }
447
448
    /**
449
     * Generates the the menu data.
450
     *
451
     * Subclasses should overwrite this method.
452
     */
453
    public function generate()
454
    {
455
    }
456
457
    /**
458
     * @return string The HTML for the menu
459
     */
460
    public function writeMenu()
461
    {
462
        return '';
463
    }
464
465
    /**
466
     * Gets an array of page rows and removes all, which are not accessible
467
     *
468
     * @param array $pages
469
     * @return array
470
     */
471
    protected function removeInaccessiblePages(array $pages)
472
    {
473
        $banned = $this->getBannedUids();
474
        $filteredPages = [];
475
        foreach ($pages as $aPage) {
476
            if ($this->filterMenuPages($aPage, $banned, (int)$aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
477
                $filteredPages[$aPage['uid']] = $aPage;
478
            }
479
        }
480
        return $filteredPages;
481
    }
482
483
    /**
484
     * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
485
     *
486
     * @return array
487
     */
488
    protected function prepareMenuItems()
489
    {
490
        $menuItems = [];
491
        $alternativeSortingField = trim($this->mconf['alternativeSortingField']) ?: 'sorting';
492
493
        // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
494
        $additionalWhere = $this->parent_cObj->stdWrapValue('additionalWhere', $this->mconf);
495
        $additionalWhere .= $this->getDoktypeExcludeWhere();
496
497
        // ... only for the FIRST level of a HMENU
498
        if ($this->menuNumber == 1 && $this->conf['special']) {
499
            $value = $this->parent_cObj->stdWrapValue('value', $this->conf['special.'] ?? [], null);
500
            switch ($this->conf['special']) {
501
                case 'userfunction':
502
                    $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
503
                    break;
504
                case 'language':
505
                    $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
506
                    break;
507
                case 'directory':
508
                    $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
509
                    break;
510
                case 'list':
511
                    $menuItems = $this->prepareMenuItemsForListMenu($value);
512
                    break;
513
                case 'updated':
514
                    $menuItems = $this->prepareMenuItemsForUpdatedMenu(
515
                        $value,
516
                        $this->mconf['alternativeSortingField'] ?: false
0 ignored issues
show
Bug introduced by
It seems like $this->mconf['alternativeSortingField'] ?: false can also be of type false; however, parameter $sortingField of TYPO3\CMS\Frontend\Conte...nuItemsForUpdatedMenu() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

918
            /** @scrutinizer ignore-type */ $this->parent_cObj->stdWrapValue('entryLevel', $this->conf['special.'] ?? []),
Loading history...
919
            $this->tmpl->rootLine
920
        );
921
        $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
922
        // Which field is for keywords
923
        $kfield = 'keywords';
924
        if ($this->conf['special.']['keywordsField']) {
925
            [$kfield] = explode(' ', trim($this->conf['special.']['keywordsField']));
926
        }
927
        // If there are keywords and the startuid is present
928
        if ($kw && $startUid) {
929
            $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
930
            $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
931
            $kwArr = GeneralUtility::trimExplode(',', $kw, true);
932
            $keyWordsWhereArr = [];
933
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
934
            foreach ($kwArr as $word) {
935
                $keyWordsWhereArr[] = $queryBuilder->expr()->like(
936
                    $kfield,
937
                    $queryBuilder->createNamedParameter(
938
                        '%' . $queryBuilder->escapeLikeWildcards($word) . '%',
939
                        \PDO::PARAM_STR
940
                    )
941
                );
942
            }
943
            $queryBuilder
944
                ->select('*')
945
                ->from('pages')
946
                ->where(
947
                    $queryBuilder->expr()->in(
948
                        'uid',
949
                        GeneralUtility::intExplode(',', $id_list, true)
950
                    ),
951
                    $queryBuilder->expr()->neq(
952
                        'uid',
953
                        $queryBuilder->createNamedParameter($specialValue, \PDO::PARAM_INT)
954
                    )
955
                );
956
957
            if (!empty($keyWordsWhereArr)) {
958
                $queryBuilder->andWhere($queryBuilder->expr()->orX(...$keyWordsWhereArr));
959
            }
960
961
            if (!empty($this->excludedDoktypes)) {
962
                $queryBuilder->andWhere(
963
                    $queryBuilder->expr()->notIn(
964
                        'pages.doktype',
965
                        $this->excludedDoktypes
966
                    )
967
                );
968
            }
969
970
            if (!$this->conf['includeNotInMenu']) {
971
                $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.nav_hide', 0));
972
            }
973
974
            if ($this->conf['special.']['excludeNoSearchPages']) {
975
                $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.no_search', 0));
976
            }
977
978
            if ($limit > 0) {
979
                $queryBuilder->setMaxResults($limit);
980
            }
981
982
            if ($sortingField) {
983
                $queryBuilder->orderBy($sortingField);
984
            } else {
985
                $queryBuilder->orderBy($sortField, 'desc');
986
            }
987
988
            $result = $queryBuilder->execute();
989
            while ($row = $result->fetch()) {
990
                $tsfe->sys_page->versionOL('pages', $row, true);
991
                if (is_array($row)) {
992
                    $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
993
                }
994
            }
995
        }
996
997
        return $menuItems;
998
    }
999
1000
    /**
1001
     * Fetches all menuitems if special = rootline is set
1002
     *
1003
     * @return array
1004
     */
1005
    protected function prepareMenuItemsForRootlineMenu()
1006
    {
1007
        $menuItems = [];
1008
        $range = $this->parent_cObj->stdWrapValue('range', $this->conf['special.'] ?? []);
1009
        $begin_end = explode('|', $range);
1010
        $begin_end[0] = (int)$begin_end[0];
1011
        if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1012
            $begin_end[1] = -1;
1013
        }
1014
        $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1015
        $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1016
        if ($endKey < $beginKey) {
1017
            $endKey = $beginKey;
1018
        }
1019
        $rl_MParray = [];
1020
        foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1021
            // For overlaid mount points, set the variable right now:
1022
            if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1023
                $rl_MParray[] = $v_rl['_MP_PARAM'];
1024
            }
1025
            // Traverse rootline:
1026
            if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1027
                $temp_key = $k_rl;
1028
                $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1029
                if (!empty($menuItems[$temp_key])) {
1030
                    // If there are no specific target for the page, put the level specific target on.
1031
                    if (!$menuItems[$temp_key]['target']) {
1032
                        $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1033
                        $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1034
                    }
1035
                } else {
1036
                    unset($menuItems[$temp_key]);
1037
                }
1038
            }
1039
            // For normal mount points, set the variable for next level.
1040
            if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1041
                $rl_MParray[] = $v_rl['_MP_PARAM'];
1042
            }
1043
        }
1044
        // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1045
        if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1046
            $menuItems = array_reverse($menuItems);
1047
        }
1048
        return $menuItems;
1049
    }
1050
1051
    /**
1052
     * Fetches all menuitems if special = browse is set
1053
     *
1054
     * @param string $specialValue The value from special.value
1055
     * @param string $sortingField The sorting field
1056
     * @param string $additionalWhere Additional WHERE clause
1057
     * @return array
1058
     */
1059
    protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1060
    {
1061
        $menuItems = [];
1062
        [$specialValue] = GeneralUtility::intExplode(',', $specialValue);
1063
        if (!$specialValue) {
1064
            $specialValue = $this->getTypoScriptFrontendController()->page['uid'];
1065
        }
1066
        // Will not work out of rootline
1067
        if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1068
            $recArr = [];
1069
            // The page record of the 'value'.
1070
            $value_rec = $this->sys_page->getPage($specialValue);
1071
            // 'up' page cannot be outside rootline
1072
            if ($value_rec['pid']) {
1073
                // The page record of 'up'.
1074
                $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1075
            }
1076
            // If the 'up' item was NOT level 0 in rootline...
1077
            if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1078
                // The page record of "index".
1079
                $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1080
            }
1081
            // check if certain pages should be excluded
1082
            $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1083
            if ($this->conf['special.']['excludeNoSearchPages']) {
1084
                $additionalWhere .= ' AND pages.no_search=0';
1085
            }
1086
            // prev / next is found
1087
            $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1088
            $lastKey = 0;
1089
            $nextActive = 0;
1090
            foreach ($prevnext_menu as $k_b => $v_b) {
1091
                if ($nextActive) {
1092
                    $recArr['next'] = $v_b;
1093
                    $nextActive = 0;
1094
                }
1095
                if ($v_b['uid'] == $specialValue) {
1096
                    if ($lastKey) {
1097
                        $recArr['prev'] = $prevnext_menu[$lastKey];
1098
                    }
1099
                    $nextActive = 1;
1100
                }
1101
                $lastKey = $k_b;
1102
            }
1103
1104
            $recArr['first'] = reset($prevnext_menu);
1105
            $recArr['last'] = end($prevnext_menu);
1106
            // prevsection / nextsection is found
1107
            // You can only do this, if there is a valid page two levels up!
1108
            if (!empty($recArr['index']['uid'])) {
1109
                $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1110
                $lastKey = 0;
1111
                $nextActive = 0;
1112
                foreach ($prevnextsection_menu as $k_b => $v_b) {
1113
                    if ($nextActive) {
1114
                        $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1115
                        if (!empty($sectionRec_temp)) {
1116
                            $recArr['nextsection'] = reset($sectionRec_temp);
1117
                            $recArr['nextsection_last'] = end($sectionRec_temp);
1118
                            $nextActive = 0;
1119
                        }
1120
                    }
1121
                    if ($v_b['uid'] == $value_rec['pid']) {
1122
                        if ($lastKey) {
1123
                            $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1124
                            if (!empty($sectionRec_temp)) {
1125
                                $recArr['prevsection'] = reset($sectionRec_temp);
1126
                                $recArr['prevsection_last'] = end($sectionRec_temp);
1127
                            }
1128
                        }
1129
                        $nextActive = 1;
1130
                    }
1131
                    $lastKey = $k_b;
1132
                }
1133
            }
1134
            if ($this->conf['special.']['items.']['prevnextToSection']) {
1135
                if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1136
                    $recArr['prev'] = $recArr['prevsection_last'];
1137
                }
1138
                if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1139
                    $recArr['next'] = $recArr['nextsection'];
1140
                }
1141
            }
1142
            $items = explode('|', $this->conf['special.']['items']);
1143
            $c = 0;
1144
            foreach ($items as $k_b => $v_b) {
1145
                $v_b = strtolower(trim($v_b));
1146
                if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1147
                    $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1148
                }
1149
                if (is_array($recArr[$v_b])) {
1150
                    $menuItems[$c] = $recArr[$v_b];
1151
                    if ($this->conf['special.'][$v_b . '.']['target']) {
1152
                        $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1153
                    }
1154
                    $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1155
                    if (is_array($tmpSpecialFields)) {
1156
                        foreach ($tmpSpecialFields as $fk => $val) {
1157
                            $menuItems[$c][$fk] = $val;
1158
                        }
1159
                    }
1160
                    $c++;
1161
                }
1162
            }
1163
        }
1164
        return $menuItems;
1165
    }
1166
1167
    /**
1168
     * Checks if a page is OK to include in the final menu item array. Pages can be excluded if the doktype is wrong,
1169
     * if they are hidden in navigation, have a uid in the list of banned uids etc.
1170
     *
1171
     * @param array $data Array of menu items
1172
     * @param array $banUidArray Array of page uids which are to be excluded
1173
     * @param bool $isSpacerPage If set, then the page is a spacer.
1174
     * @return bool Returns TRUE if the page can be safely included.
1175
     *
1176
     * @throws \UnexpectedValueException
1177
     */
1178
    public function filterMenuPages(&$data, $banUidArray, $isSpacerPage)
1179
    {
1180
        $includePage = true;
1181
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] ?? [] as $className) {
1182
            $hookObject = GeneralUtility::makeInstance($className);
1183
            if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1184
                throw new \UnexpectedValueException($className . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1185
            }
1186
            $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $isSpacerPage, $this);
1187
        }
1188
        if (!$includePage) {
1189
            return false;
1190
        }
1191
        if ($data['_SAFE']) {
1192
            return true;
1193
        }
1194
        // If the spacer-function is not enabled, spacers will not enter the $menuArr
1195
        if (!$this->mconf['SPC'] && $isSpacerPage) {
1196
            return false;
1197
        }
1198
        // Page may not be a 'Backend User Section' or any other excluded doktype
1199
        if (in_array((int)$data['doktype'], $this->excludedDoktypes, true)) {
1200
            return false;
1201
        }
1202
        // PageID should not be banned
1203
        if (in_array((int)$data['uid'], $banUidArray, true)) {
1204
            return false;
1205
        }
1206
        // If the page is hide in menu, but the menu does not include them do not show the page
1207
        if ($data['nav_hide'] && !$this->conf['includeNotInMenu']) {
1208
            return false;
1209
        }
1210
        // Checking if a page should be shown in the menu depending on whether a translation exists or if the default language is disabled
1211
        if (!$this->sys_page->isPageSuitableForLanguage($data, $this->getCurrentLanguageAspect())) {
1212
            return false;
1213
        }
1214
        // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1215
        if ($this->getCurrentLanguageAspect()->getId() > 0 && $this->conf['protectLvar']) {
1216
            if ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1217
                $olRec = $this->sys_page->getPageOverlay($data['uid'], $this->getCurrentLanguageAspect()->getId());
1218
                if (empty($olRec)) {
1219
                    // If no page translation record then page can NOT be accessed in
1220
                    // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1221
                    $data['_ADD_GETVARS'] .= '&L=0';
1222
                }
1223
            }
1224
        }
1225
        return true;
1226
    }
1227
1228
    /**
1229
     * Generating the per-menu-item configuration arrays based on the settings for item states (NO, ACT, CUR etc)
1230
     * set in ->mconf (config for the current menu object)
1231
     * Basically it will produce an individual array for each menu item based on the item states.
1232
     * BUT in addition the "optionSplit" syntax for the values is ALSO evaluated here so that all property-values
1233
     * are "option-splitted" and the output will thus be resolved.
1234
     * Is called from the "generate" functions in the extension classes. The function is processor intensive due to
1235
     * the option split feature in particular. But since the generate function is not always called
1236
     * (since the ->result array may be cached, see makeMenu) it doesn't hurt so badly.
1237
     *
1238
     * @param int $splitCount Number of menu items in the menu
1239
     * @return array the resolved configuration for each item
1240
     */
1241
    protected function processItemStates($splitCount)
1242
    {
1243
        // Prepare normal settings
1244
        if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1245
            // Setting a blank array if NO=1 and there are no properties.
1246
            $this->mconf['NO.'] = [];
1247
        }
1248
        $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1249
        $NOconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['NO.'], $splitCount);
1250
1251
        // Prepare custom states settings, overriding normal settings
1252
        foreach (self::customItemStates as $state) {
1253
            if (empty($this->mconf[$state])) {
1254
                continue;
1255
            }
1256
            $customConfiguration = null;
1257
            foreach ($NOconf as $key => $val) {
1258
                if ($this->isItemState($state, $key)) {
1259
                    // if this is the first element of type $state, we must generate the custom configuration.
1260
                    if ($customConfiguration === null) {
1261
                        $customConfiguration = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf[$state . '.'], $splitCount);
1262
                    }
1263
                    // Substitute normal with the custom (e.g. IFSUB)
1264
                    if (isset($customConfiguration[$key])) {
1265
                        $NOconf[$key] = $customConfiguration[$key];
1266
                    }
1267
                }
1268
            }
1269
        }
1270
1271
        return $NOconf;
1272
    }
1273
1274
    /**
1275
     * Creates the URL, target and onclick values for the menu item link. Returns them in an array as key/value pairs for <A>-tag attributes
1276
     * This function doesn't care about the url, because if we let the url be redirected, it will be logged in the stat!!!
1277
     *
1278
     * @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)
1279
     * @param string $altTarget Alternative target
1280
     * @param string $typeOverride Alternative type
1281
     * @return array Returns an array with A-tag attributes as key/value pairs (HREF, TARGET and onClick)
1282
     */
1283
    protected function link($key, $altTarget, $typeOverride)
1284
    {
1285
        $runtimeCache = $this->getRuntimeCache();
1286
        $MP_var = $this->getMPvar($key);
1287
        $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . $MP_var . json_encode($this->menuArr[$key]));
1288
        $runtimeCachedLink = $runtimeCache->get($cacheId);
1289
        if ($runtimeCachedLink !== false) {
1290
            return $runtimeCachedLink;
1291
        }
1292
1293
        $tsfe = $this->getTypoScriptFrontendController();
1294
1295
        // If a user script returned the value overrideId in the menu array we use that as page id
1296
        if ($this->mconf['overrideId'] || $this->menuArr[$key]['overrideId']) {
1297
            $overrideId = (int)($this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId']);
1298
            $overrideId = $overrideId > 0 ? $overrideId : null;
1299
            // Clear MP parameters since ID was changed.
1300
            $MP_params = '';
1301
        } else {
1302
            $overrideId = null;
1303
            // Mount points:
1304
            $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1305
        }
1306
        // Setting main target:
1307
        if ($altTarget) {
1308
            $mainTarget = $altTarget;
1309
        } else {
1310
            $mainTarget = $this->parent_cObj->stdWrapValue('target', $this->mconf);
1311
        }
1312
        // Creating link:
1313
        $addParams = $this->mconf['addParams'] . $MP_params;
1314
        if ($this->mconf['collapse'] && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1315
            $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1316
            $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1317
            $LD = $this->menuTypoLink($thePage, $mainTarget, $addParams, $typeOverride, $overrideId);
1318
        } else {
1319
            $addParams .= $this->I['val']['additionalParams'] . $this->menuArr[$key]['_ADD_GETVARS'];
1320
            $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, $addParams, $typeOverride, $overrideId);
1321
        }
1322
        // Override default target configuration if option is set
1323
        if ($this->menuArr[$key]['target']) {
1324
            $LD['target'] = $this->menuArr[$key]['target'];
1325
        }
1326
        // Override URL if using "External URL"
1327
        if ((int)$this->menuArr[$key]['doktype'] === PageRepository::DOKTYPE_LINK) {
1328
            $externalUrl = $this->sys_page->getExtURL($this->menuArr[$key]);
1329
            // Create link using typolink (concerning spamProtectEmailAddresses) for email links
1330
            $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $externalUrl]);
1331
            // Links to emails should not have any target
1332
            if (stripos($externalUrl, 'mailto:') === 0) {
1333
                $LD['target'] = '';
1334
            // use external target for the URL
1335
            } elseif (empty($LD['target']) && !empty($tsfe->extTarget)) {
1336
                $LD['target'] = $tsfe->extTarget;
1337
            }
1338
        }
1339
1340
        // Override url if current page is a shortcut
1341
        $shortcut = null;
1342
        if ((int)$this->menuArr[$key]['doktype'] === PageRepository::DOKTYPE_SHORTCUT && (int)$this->menuArr[$key]['shortcut_mode'] !== PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1343
            $menuItem = $this->menuArr[$key];
1344
            try {
1345
                $shortcut = $tsfe->sys_page->getPageShortcut(
1346
                    $menuItem['shortcut'],
1347
                    $menuItem['shortcut_mode'],
1348
                    $menuItem['uid'],
1349
                    20,
1350
                    [],
1351
                    true
1352
                );
1353
            } catch (\Exception $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1354
            }
1355
            if (!is_array($shortcut)) {
1356
                $runtimeCache->set($cacheId, []);
1357
                return [];
1358
            }
1359
            // Only setting url, not target
1360
            $LD['totalURL'] = $this->parent_cObj->typoLink_URL([
1361
                'parameter' => $shortcut['uid'],
1362
                'language' => 'current',
1363
                'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1364
                'linkAccessRestrictedPages' => !empty($this->mconf['showAccessRestrictedPages'])
1365
            ]);
1366
        }
1367
        if ($shortcut) {
1368
            $pageData = $shortcut;
1369
            $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1370
        } else {
1371
            $pageData = $this->menuArr[$key];
1372
        }
1373
        // Manipulation in case of access restricted pages:
1374
        $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1375
        // Overriding URL / Target if set to do so:
1376
        if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
1377
            $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1378
            if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1379
                $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1380
            }
1381
        }
1382
        // OnClick open in windows.
1383
        $onClick = '';
1384
        if ($this->mconf['JSWindow']) {
1385
            $conf = $this->mconf['JSWindow.'];
1386
            $url = $LD['totalURL'];
1387
            $LD['totalURL'] = '#';
1388
            $onClick = 'openPic('
1389
                . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1390
                . '\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1391
                . GeneralUtility::quoteJSvalue($conf['params']) . '); return false;';
1392
            GeneralUtility::makeInstance(AssetCollector::class)->addInlineJavaScript('openPic', 'function openPic(url, winName, winParams) { var theWindow = window.open(url, winName, winParams); if (theWindow) { theWindow.focus(); } }');
1393
        }
1394
        // look for type and popup
1395
        // following settings are valid in field target:
1396
        // 230								will add type=230 to the link
1397
        // 230 500x600						will add type=230 to the link and open in popup window with 500x600 pixels
1398
        // 230 _blank						will add type=230 to the link and open with target "_blank"
1399
        // 230x450:resizable=0,location=1	will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1400
        $matches = [];
1401
        $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1402
        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...
1403
            // has type?
1404
            if ((int)$matches[1] || $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...
1405
                $LD['totalURL'] .= (strpos($LD['totalURL'], '?') === false ? '?' : '&') . 'type=' . ($targetIsType ?: (int)$matches[1]);
1406
                $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1407
            }
1408
            // Open in popup window?
1409
            if ($matches[3] && $matches[4]) {
1410
                $target = $LD['target'] ?? 'FEopenLink';
1411
                $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1412
                $onClick = 'vHWin=window.open('
1413
                    . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1414
                    . ',' . GeneralUtility::quoteJSvalue($target) . ',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1415
                $LD['target'] = '';
1416
            }
1417
        }
1418
        // out:
1419
        $list = [];
1420
        // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1421
        // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1422
        // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1423
        $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1424
        $list['TARGET'] = $LD['target'];
1425
        $list['onClick'] = $onClick;
1426
        $runtimeCache->set($cacheId, $list);
1427
        return $list;
1428
    }
1429
1430
    /**
1431
     * Determines original shortcut destination in page overlays.
1432
     *
1433
     * Since the pages records used for menu rendering are overlaid by default,
1434
     * the original 'shortcut' value is lost, if a translation did not define one.
1435
     *
1436
     * @param array $page
1437
     * @return array
1438
     */
1439
    protected function determineOriginalShortcutPage(array $page)
1440
    {
1441
        // Check if modification is required
1442
        if (
1443
            $this->getCurrentLanguageAspect()->getId() > 0
1444
            && empty($page['shortcut'])
1445
            && !empty($page['uid'])
1446
            && !empty($page['_PAGES_OVERLAY'])
1447
            && !empty($page['_PAGES_OVERLAY_UID'])
1448
        ) {
1449
            // Using raw record since the record was overlaid and is correct already:
1450
            $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1451
1452
            if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1453
                $page['shortcut'] = $originalPage['shortcut'];
1454
            }
1455
        }
1456
1457
        return $page;
1458
    }
1459
1460
    /**
1461
     * Will change $LD (passed by reference) if the page is access restricted
1462
     *
1463
     * @param array $LD The array from the linkData() function
1464
     * @param array $page Page array
1465
     * @param string $mainTarget Main target value
1466
     * @param string $typeOverride Type number override if any
1467
     */
1468
    protected function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1469
    {
1470
        // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1471
        if ($this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1472
            $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1473
            $addParams = str_replace(
1474
                [
1475
                    '###RETURN_URL###',
1476
                    '###PAGE_ID###'
1477
                ],
1478
                [
1479
                    rawurlencode($LD['totalURL']),
1480
                    $page['_SHORTCUT_PAGE_UID'] ?? $page['uid']
1481
                ],
1482
                $this->mconf['showAccessRestrictedPages.']['addParams']
1483
            );
1484
            $LD = $this->menuTypoLink($thePage, $mainTarget, $addParams, $typeOverride);
1485
        }
1486
    }
1487
1488
    /**
1489
     * Creates a submenu level to the current level - if configured for.
1490
     *
1491
     * @param int $uid Page id of the current page for which a submenu MAY be produced (if conditions are met)
1492
     * @param string $objSuffix Object prefix, see ->start()
1493
     * @return string HTML content of the submenu
1494
     */
1495
    protected function subMenu($uid, $objSuffix)
1496
    {
1497
        // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1498
        $altArray = '';
1499
        if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU']) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1500
            $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1501
        }
1502
        // Make submenu if the page is the next active
1503
        $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix];
1504
        // stdWrap for expAll
1505
        $this->mconf['expAll'] = $this->parent_cObj->stdWrapValue('expAll', $this->mconf);
1506
        if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !$this->mconf['sectionIndex']) {
1507
            try {
1508
                $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1509
                /** @var AbstractMenuContentObject $submenu */
1510
                $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1511
                $submenu->entryLevel = $this->entryLevel + 1;
1512
                $submenu->rL_uidRegister = $this->rL_uidRegister;
1513
                $submenu->MP_array = $this->MP_array;
1514
                if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1515
                    $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1516
                }
1517
                // Especially scripts that build the submenu needs the parent data
1518
                $submenu->parent_cObj = $this->parent_cObj;
1519
                $submenu->setParentMenu($this->menuArr, $this->I['key']);
1520
                // Setting alternativeMenuTempArray (will be effective only if an array)
1521
                if (is_array($altArray)) {
1522
                    $submenu->alternativeMenuTempArray = $altArray;
0 ignored issues
show
Documentation Bug introduced by
It seems like $altArray of type array is incompatible with the declared type string of property $alternativeMenuTempArray.

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

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

Loading history...
1523
                }
1524
                if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1525
                    $submenu->makeMenu();
1526
                    // Memorize the current menu item count
1527
                    $tsfe = $this->getTypoScriptFrontendController();
1528
                    $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1529
                    // Reset the menu item count for the submenu
1530
                    $tsfe->register['count_MENUOBJ'] = 0;
1531
                    $content = $submenu->writeMenu();
1532
                    // Restore the item count now that the submenu has been handled
1533
                    $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1534
                    $tsfe->register['count_menuItems'] = count($this->menuArr);
1535
                    return $content;
1536
                }
1537
            } catch (NoSuchMenuTypeException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1538
            }
1539
        }
1540
        return '';
1541
    }
1542
1543
    /**
1544
     * Returns TRUE if the page with UID $uid is the NEXT page in root line (which means a submenu should be drawn)
1545
     *
1546
     * @param int $uid Page uid to evaluate.
1547
     * @param string $MPvar MPvar for the current position of item.
1548
     * @return bool TRUE if page with $uid is active
1549
     * @see subMenu()
1550
     */
1551
    protected function isNext($uid, $MPvar)
1552
    {
1553
        // Check for always active PIDs:
1554
        if (in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1555
            return true;
1556
        }
1557
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1558
        if ($uid && $testUid == $this->nextActive) {
1559
            return true;
1560
        }
1561
        return false;
1562
    }
1563
1564
    /**
1565
     * Returns TRUE if the page with UID $uid is active (in the current rootline)
1566
     *
1567
     * @param int $uid Page uid to evaluate.
1568
     * @param string $MPvar MPvar for the current position of item.
1569
     * @return bool TRUE if page with $uid is active
1570
     */
1571
    protected function isActive($uid, $MPvar)
1572
    {
1573
        // Check for always active PIDs:
1574
        if (in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1575
            return true;
1576
        }
1577
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1578
        if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1579
            return true;
1580
        }
1581
        return false;
1582
    }
1583
1584
    /**
1585
     * Returns TRUE if the page with UID $uid is the CURRENT page (equals $this->getTypoScriptFrontendController()->id)
1586
     *
1587
     * @param int $uid Page uid to evaluate.
1588
     * @param string $MPvar MPvar for the current position of item.
1589
     * @return bool TRUE if page $uid = $this->getTypoScriptFrontendController()->id
1590
     */
1591
    protected function isCurrent($uid, $MPvar)
1592
    {
1593
        $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1594
        return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1595
    }
1596
1597
    /**
1598
     * Returns TRUE if there is a submenu with items for the page id, $uid
1599
     * Used by the item states "IFSUB", "ACTIFSUB" and "CURIFSUB" to check if there is a submenu
1600
     *
1601
     * @param int $uid Page uid for which to search for a submenu
1602
     * @return bool Returns TRUE if there was a submenu with items found
1603
     */
1604
    protected function isSubMenu($uid)
1605
    {
1606
        $cacheId = 'menucontentobject-is-submenu-decision-' . $uid;
1607
        $runtimeCache = $this->getRuntimeCache();
1608
        $cachedDecision = $runtimeCache->get($cacheId);
1609
        if (isset($cachedDecision['result'])) {
1610
            return $cachedDecision['result'];
1611
        }
1612
        // Looking for a mount-pid for this UID since if that
1613
        // exists we should look for a subpages THERE and not in the input $uid;
1614
        $mount_info = $this->sys_page->getMountPointInfo($uid);
1615
        if (is_array($mount_info)) {
1616
            $uid = $mount_info['mount_pid'];
1617
        }
1618
        $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1619
        $hasSubPages = false;
1620
        $bannedUids = $this->getBannedUids();
1621
        $languageId = $this->getCurrentLanguageAspect()->getId();
1622
        foreach ($recs as $theRec) {
1623
            // no valid subpage if the document type is excluded from the menu
1624
            if (in_array((int)($theRec['doktype'] ?? 0), $this->excludedDoktypes, true)) {
1625
                continue;
1626
            }
1627
            // No valid subpage if the page is hidden inside menus and
1628
            // it wasn't forced to show such entries
1629
            if (isset($theRec['nav_hide']) && $theRec['nav_hide']
1630
                && (!isset($this->conf['includeNotInMenu']) || !$this->conf['includeNotInMenu'])
1631
            ) {
1632
                continue;
1633
            }
1634
            // No valid subpage if the default language should be shown and the page settings
1635
            // are excluding the visibility of the default language
1636
            if (!$languageId && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'] ?? 0)) {
1637
                continue;
1638
            }
1639
            // No valid subpage if the alternative language should be shown and the page settings
1640
            // are requiring a valid overlay but it doesn't exists
1641
            $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg'] ?? null);
1642
            if ($languageId && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1643
                continue;
1644
            }
1645
            // No valid subpage if the subpage is banned by excludeUidList
1646
            if (in_array((int)$theRec['uid'], $bannedUids, true)) {
1647
                continue;
1648
            }
1649
            $hasSubPages = true;
1650
            break;
1651
        }
1652
        $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1653
        return $hasSubPages;
1654
    }
1655
1656
    /**
1657
     * Used by processItemStates() to evaluate if a menu item (identified by $key) is in a certain state.
1658
     *
1659
     * @param string $kind The item state to evaluate (SPC, IFSUB, ACT etc...)
1660
     * @param int $key Key pointing to menu item from ->menuArr
1661
     * @return bool Returns TRUE if state matches
1662
     * @see processItemStates()
1663
     */
1664
    protected function isItemState($kind, $key)
1665
    {
1666
        $natVal = false;
1667
        // If any value is set for ITEM_STATE the normal evaluation is discarded
1668
        if ($this->menuArr[$key]['ITEM_STATE'] ?? false) {
1669
            if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1670
                $natVal = true;
1671
            }
1672
        } else {
1673
            switch ($kind) {
1674
                case 'SPC':
1675
                    $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1676
                    break;
1677
                case 'IFSUB':
1678
                    $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1679
                    break;
1680
                case 'ACT':
1681
                    $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1682
                    break;
1683
                case 'ACTIFSUB':
1684
                    $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1685
                    break;
1686
                case 'CUR':
1687
                    $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
1688
                    break;
1689
                case 'CURIFSUB':
1690
                    $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1691
                    break;
1692
                case 'USR':
1693
                    $natVal = (bool)$this->menuArr[$key]['fe_group'];
1694
                    break;
1695
            }
1696
        }
1697
        return $natVal;
1698
    }
1699
1700
    /**
1701
     * Creates an access-key for a TMENU menu item based on the menu item titles first letter
1702
     *
1703
     * @param string $title Menu item title.
1704
     * @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
1705
     */
1706
    protected function accessKey($title)
1707
    {
1708
        $tsfe = $this->getTypoScriptFrontendController();
1709
        // The global array ACCESSKEY is used to globally control if letters are already used!!
1710
        $result = [];
1711
        $title = trim(strip_tags($title));
1712
        $titleLen = strlen($title);
1713
        for ($a = 0; $a < $titleLen; $a++) {
1714
            $key = strtoupper(substr($title, $a, 1));
1715
            if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
1716
                $tsfe->accessKey[$key] = true;
1717
                $result['code'] = ' accesskey="' . $key . '"';
1718
                $result['alt'] = ' (ALT+' . $key . ')';
1719
                $result['key'] = $key;
1720
                break;
1721
            }
1722
        }
1723
        return $result;
1724
    }
1725
1726
    /**
1727
     * Calls a user function for processing of internal data.
1728
     * Used for the properties "IProcFunc" and "itemArrayProcFunc"
1729
     *
1730
     * @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".
1731
     * @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
1732
     * @return mixed The processed $passVar
1733
     */
1734
    protected function userProcess($mConfKey, $passVar)
1735
    {
1736
        if ($this->mconf[$mConfKey]) {
1737
            $funcConf = $this->mconf[$mConfKey . '.'];
1738
            $funcConf['parentObj'] = $this;
1739
            $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
1740
        }
1741
        return $passVar;
1742
    }
1743
1744
    /**
1745
     * 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'])
1746
     */
1747
    protected function setATagParts()
1748
    {
1749
        $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
1750
        $params = $params !== '' ? ' ' . $params : '';
1751
        $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], true) . $params . '>';
1752
        $this->I['A2'] = '</a>';
1753
    }
1754
1755
    /**
1756
     * Returns the title for the navigation
1757
     *
1758
     * @param string $title The current page title
1759
     * @param string $nav_title The current value of the navigation title
1760
     * @return string Returns the navigation title if it is NOT blank, otherwise the page title.
1761
     */
1762
    protected function getPageTitle($title, $nav_title)
1763
    {
1764
        return trim($nav_title) !== '' ? $nav_title : $title;
1765
    }
1766
1767
    /**
1768
     * Return MPvar string for entry $key in ->menuArr
1769
     *
1770
     * @param int $key Pointer to element in ->menuArr
1771
     * @return string MP vars for element.
1772
     * @see link()
1773
     */
1774
    protected function getMPvar($key)
1775
    {
1776
        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
1777
            $localMP_array = $this->MP_array;
1778
            // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
1779
            if ($this->menuArr[$key]['_MP_PARAM']) {
1780
                $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
1781
            }
1782
            return !empty($localMP_array) ? implode(',', $localMP_array) : '';
1783
        }
1784
        return '';
1785
    }
1786
1787
    /**
1788
     * Returns where clause part to exclude 'not in menu' pages
1789
     *
1790
     * @return string where clause part.
1791
     */
1792
    protected function getDoktypeExcludeWhere()
1793
    {
1794
        return !empty($this->excludedDoktypes) ? ' AND pages.doktype NOT IN (' . implode(',', $this->excludedDoktypes) . ')' : '';
1795
    }
1796
1797
    /**
1798
     * Returns an array of banned UIDs (from excludeUidList)
1799
     *
1800
     * @return array Array of banned UIDs
1801
     */
1802
    protected function getBannedUids()
1803
    {
1804
        $excludeUidList = $this->parent_cObj->stdWrapValue('excludeUidList', $this->conf);
1805
        if (!trim($excludeUidList)) {
1806
            return [];
1807
        }
1808
1809
        $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page['uid'] ?? null, $excludeUidList);
1810
        return GeneralUtility::intExplode(',', $banUidList);
1811
    }
1812
1813
    /**
1814
     * Calls typolink to create menu item links.
1815
     *
1816
     * @param array $page Page record (uid points where to link to)
1817
     * @param string $oTarget Target frame/window
1818
     * @param string $addParams Parameters to add to URL
1819
     * @param int|string $typeOverride "type" value, empty string means "not set"
1820
     * @param int|null $overridePageId link to this page instead of the $page[uid] value
1821
     * @return array See linkData
1822
     */
1823
    protected function menuTypoLink($page, $oTarget, $addParams, $typeOverride, ?int $overridePageId = null)
1824
    {
1825
        $conf = [
1826
            'parameter' => $overridePageId ?? $page['uid']
1827
        ];
1828
        if (MathUtility::canBeInterpretedAsInteger($typeOverride)) {
1829
            $conf['parameter'] .= ',' . (int)$typeOverride;
1830
        }
1831
        if ($addParams) {
1832
            $conf['additionalParams'] = $addParams;
1833
        }
1834
1835
        // Ensure that the typolink gets an info which language was actually requested. The $page record could be the record
1836
        // from page translation language=1 as fallback but page translation language=2 was requested. Search for
1837
        // "_PAGES_OVERLAY_REQUESTEDLANGUAGE" for more details
1838
        if (isset($page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'])) {
1839
            $conf['language'] = $page['_PAGES_OVERLAY_REQUESTEDLANGUAGE'];
1840
        }
1841
        if ($oTarget) {
1842
            $conf['target'] = $oTarget;
1843
        }
1844
        if ($page['sectionIndex_uid'] ?? false) {
1845
            $conf['section'] = $page['sectionIndex_uid'];
1846
        }
1847
        $conf['linkAccessRestrictedPages'] = !empty($this->mconf['showAccessRestrictedPages']);
1848
        $this->parent_cObj->typoLink('|', $conf);
1849
        $LD = $this->parent_cObj->lastTypoLinkLD;
1850
        $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
1851
        return $LD;
1852
    }
1853
1854
    /**
1855
     * Generates a list of content objects with sectionIndex enabled
1856
     * available on a specific page
1857
     *
1858
     * Used for menus with sectionIndex enabled
1859
     *
1860
     * @param string $altSortField Alternative sorting field
1861
     * @param int $pid The page id to search for sections
1862
     * @throws \UnexpectedValueException if the query to fetch the content elements unexpectedly fails
1863
     * @return array
1864
     */
1865
    protected function sectionIndex($altSortField, $pid = null)
1866
    {
1867
        $pid = (int)($pid ?: $this->id);
1868
        $basePageRow = $this->sys_page->getPage($pid);
1869
        if (!is_array($basePageRow)) {
0 ignored issues
show
introduced by
The condition is_array($basePageRow) is always true.
Loading history...
1870
            return [];
1871
        }
1872
        $useColPos = (int)$this->parent_cObj->stdWrapValue('useColPos', $this->mconf['sectionIndex.'] ?? [], 0);
1873
        $selectSetup = [
1874
            'pidInList' => $pid,
1875
            'orderBy' => $altSortField,
1876
            'languageField' => 'sys_language_uid',
1877
            'where' => ''
1878
        ];
1879
1880
        if ($useColPos >= 0) {
1881
            $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1882
                ->getConnectionForTable('tt_content')
1883
                ->getExpressionBuilder();
1884
            $selectSetup['where'] = $expressionBuilder->eq('colPos', $useColPos);
1885
        }
1886
1887
        if ($basePageRow['content_from_pid'] ?? false) {
1888
            // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
1889
            // the referenced page
1890
            $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
1891
        }
1892
        $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
1893
        if (!$statement) {
0 ignored issues
show
introduced by
$statement is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
1894
            $message = 'SectionIndex: Query to fetch the content elements failed!';
1895
            throw new \UnexpectedValueException($message, 1337334849);
1896
        }
1897
        $result = [];
1898
        while ($row = $statement->fetch()) {
1899
            $this->sys_page->versionOL('tt_content', $row);
1900
            if ($this->getCurrentLanguageAspect()->doOverlays() && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
1901
                $row = $this->sys_page->getRecordOverlay(
1902
                    'tt_content',
1903
                    $row,
1904
                    $basePageRow['_PAGES_OVERLAY_LANGUAGE'],
1905
                    $this->getCurrentLanguageAspect()->getOverlayType() === LanguageAspect::OVERLAYS_MIXED ? '1' : 'hideNonTranslated'
1906
                );
1907
            }
1908
            if ($this->mconf['sectionIndex.']['type'] !== 'all') {
1909
                $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
1910
                $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
1911
                $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $isValidHeader = ((int)$...$row['header']) !== ''), Probably Intended Meaning: $isValidHeader = (int)$r...$row['header']) !== '')
Loading history...
1912
                if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
1913
                    continue;
1914
                }
1915
            }
1916
            if (is_array($row)) {
1917
                $uid = $row['uid'] ?? null;
1918
                $result[$uid] = $basePageRow;
1919
                $result[$uid]['title'] = $row['header'];
1920
                $result[$uid]['nav_title'] = $row['header'];
1921
                // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
1922
                $result[$uid]['nav_hide'] = 0;
1923
                $result[$uid]['subtitle'] = $row['subheader'] ?? '';
1924
                $result[$uid]['starttime'] = $row['starttime'] ?? '';
1925
                $result[$uid]['endtime'] = $row['endtime'] ?? '';
1926
                $result[$uid]['fe_group'] = $row['fe_group'] ?? '';
1927
                $result[$uid]['media'] = $row['media'] ?? '';
1928
                $result[$uid]['header_layout'] = $row['header_layout'] ?? '';
1929
                $result[$uid]['bodytext'] = $row['bodytext'] ?? '';
1930
                $result[$uid]['image'] = $row['image'] ?? '';
1931
                $result[$uid]['sectionIndex_uid'] = $uid;
1932
            }
1933
        }
1934
1935
        return $result;
1936
    }
1937
1938
    /**
1939
     * Returns the sys_page object
1940
     *
1941
     * @return PageRepository
1942
     */
1943
    public function getSysPage()
1944
    {
1945
        return $this->sys_page;
1946
    }
1947
1948
    /**
1949
     * Returns the parent content object
1950
     *
1951
     * @return ContentObjectRenderer
1952
     */
1953
    public function getParentContentObject()
1954
    {
1955
        return $this->parent_cObj;
1956
    }
1957
1958
    /**
1959
     * @return TypoScriptFrontendController
1960
     */
1961
    protected function getTypoScriptFrontendController()
1962
    {
1963
        return $GLOBALS['TSFE'];
1964
    }
1965
1966
    protected function getCurrentLanguageAspect(): LanguageAspect
1967
    {
1968
        return GeneralUtility::makeInstance(Context::class)->getAspect('language');
1969
    }
1970
1971
    /**
1972
     * @return TimeTracker
1973
     */
1974
    protected function getTimeTracker()
1975
    {
1976
        return GeneralUtility::makeInstance(TimeTracker::class);
1977
    }
1978
1979
    /**
1980
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
1981
     */
1982
    protected function getCache()
1983
    {
1984
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
1985
    }
1986
1987
    /**
1988
     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
1989
     */
1990
    protected function getRuntimeCache()
1991
    {
1992
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
1993
    }
1994
1995
    /**
1996
     * Returns the currently configured "site" if a site is configured (= resolved) in the current request.
1997
     *
1998
     * @return SiteInterface
1999
     */
2000
    protected function getCurrentSite(): SiteInterface
2001
    {
2002
        try {
2003
            return GeneralUtility::makeInstance(SiteFinder::class)
2004
                ->getSiteByPageId((int)$this->getTypoScriptFrontendController()->id);
2005
        } catch (SiteNotFoundException $e) {
2006
            return new NullSite();
2007
        }
2008
    }
2009
2010
    /**
2011
     * Set the parentMenuArr and key to provide the parentMenu information to the
2012
     * subMenu, special fur IProcFunc and itemArrayProcFunc user functions.
2013
     *
2014
     * @param array $menuArr
2015
     * @param int $menuItemKey
2016
     * @internal
2017
     */
2018
    public function setParentMenu(array $menuArr, $menuItemKey)
2019
    {
2020
        // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
2021
        if (is_array($menuArr)
2022
            && (is_int($menuItemKey) && $menuItemKey >= 0 && isset($menuArr[$menuItemKey]))
2023
        ) {
2024
            $this->parentMenuArr = $menuArr;
2025
            $this->parentMenuArrItemKey = $menuItemKey;
2026
        }
2027
    }
2028
2029
    /**
2030
     * Check if there is a valid parentMenuArr.
2031
     *
2032
     * @return bool
2033
     */
2034
    protected function hasParentMenuArr()
2035
    {
2036
        return
2037
            $this->menuNumber > 1
2038
            && is_array($this->parentMenuArr)
2039
            && !empty($this->parentMenuArr)
2040
        ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
2041
    }
2042
2043
    /**
2044
     * Check if we have a parentMenuArrItemKey
2045
     */
2046
    protected function hasParentMenuItemKey()
2047
    {
2048
        return null !== $this->parentMenuArrItemKey;
2049
    }
2050
2051
    /**
2052
     * Check if the the parentMenuItem exists
2053
     */
2054
    protected function hasParentMenuItem()
2055
    {
2056
        return
2057
            $this->hasParentMenuArr()
2058
            && $this->hasParentMenuItemKey()
2059
            && isset($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2060
        ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
2061
    }
2062
2063
    /**
2064
     * Get the parentMenuArr, if this is subMenu.
2065
     *
2066
     * @return array
2067
     */
2068
    public function getParentMenuArr()
2069
    {
2070
        return $this->hasParentMenuArr() ? $this->parentMenuArr : [];
2071
    }
2072
2073
    /**
2074
     * Get the parentMenuItem from the parentMenuArr, if this is a subMenu
2075
     *
2076
     * @return array|null
2077
     */
2078
    public function getParentMenuItem()
2079
    {
2080
        // check if we have a parentMenuItem and if it is an array
2081
        if ($this->hasParentMenuItem()
2082
            && is_array($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2083
        ) {
2084
            return $this->getParentMenuArr()[$this->parentMenuArrItemKey];
2085
        }
2086
2087
        return null;
2088
    }
2089
}
2090