Completed
Push — master ( 0fb970...2f9d14 )
by
unknown
18:05
created

prepareMenuItemsForDirectoryMenu()   D

Complexity

Conditions 17
Paths 218

Size

Total Lines 64
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 17
eloc 35
c 2
b 0
f 0
nc 218
nop 2
dl 0
loc 64
rs 4.1583

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
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\Site\Entity\NullSite;
26
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
27
use TYPO3\CMS\Core\Site\SiteFinder;
28
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
29
use TYPO3\CMS\Core\TypoScript\TemplateService;
30
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Core\Utility\MathUtility;
33
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
34
use TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException;
35
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
36
use TYPO3\CMS\Frontend\Typolink\PageLinkBuilder;
37
38
/**
39
 * Generating navigation/menus from TypoScript
40
 *
41
 * The HMENU content object uses this (or more precisely one of the extension classes).
42
 * Among others the class generates an array of menu items. Thereafter functions from the subclasses are called.
43
 * The class is always used through extension classes like TextMenuContentObject.
44
 */
45
abstract class AbstractMenuContentObject
46
{
47
    /**
48
     * tells you which menu number this is. This is important when getting data from the setup
49
     *
50
     * @var int
51
     */
52
    protected $menuNumber = 1;
53
54
    /**
55
     * 0 = rootFolder
56
     *
57
     * @var int
58
     */
59
    protected $entryLevel = 0;
60
61
    /**
62
     * Doktypes that define which should not be included in a menu
63
     *
64
     * @var int[]
65
     */
66
    protected $excludedDoktypes = [PageRepository::DOKTYPE_BE_USER_SECTION, PageRepository::DOKTYPE_SYSFOLDER];
67
68
    /**
69
     * @var int[]
70
     */
71
    protected $alwaysActivePIDlist = [];
72
73
    /**
74
     * Loaded with the parent cObj-object when a new HMENU is made
75
     *
76
     * @var ContentObjectRenderer
77
     */
78
    public $parent_cObj;
79
80
    /**
81
     * accumulation of mount point data
82
     *
83
     * @var string[]
84
     */
85
    protected $MP_array = [];
86
87
    /**
88
     * HMENU configuration
89
     *
90
     * @var array
91
     */
92
    protected $conf = [];
93
94
    /**
95
     * xMENU configuration (TMENU etc)
96
     *
97
     * @var array
98
     */
99
    protected $mconf = [];
100
101
    /**
102
     * @var TemplateService
103
     */
104
    protected $tmpl;
105
106
    /**
107
     * @var PageRepository
108
     */
109
    protected $sys_page;
110
111
    /**
112
     * The base page-id of the menu.
113
     *
114
     * @var int
115
     */
116
    protected $id;
117
118
    /**
119
     * Holds the page uid of the NEXT page in the root line from the page pointed to by entryLevel;
120
     * Used to expand the menu automatically if in a certain root line.
121
     *
122
     * @var string
123
     */
124
    protected $nextActive;
125
126
    /**
127
     * The array of menuItems which is built
128
     *
129
     * @var array[]
130
     */
131
    protected $menuArr;
132
133
    /**
134
     * @var string
135
     */
136
    protected $hash;
137
138
    /**
139
     * @var array
140
     */
141
    protected $result = [];
142
143
    /**
144
     * Is filled with an array of page uid numbers + RL parameters which are in the current
145
     * root line (used to evaluate whether a menu item is in active state)
146
     *
147
     * @var array
148
     */
149
    protected $rL_uidRegister;
150
151
    /**
152
     * @var mixed[]
153
     */
154
    protected $I;
155
156
    /**
157
     * @var string
158
     */
159
    protected $WMresult;
160
161
    /**
162
     * @var int
163
     */
164
    protected $WMmenuItems;
165
166
    /**
167
     * @var array[]
168
     */
169
    protected $WMsubmenuObjSuffixes;
170
171
    /**
172
     * @var ContentObjectRenderer
173
     */
174
    protected $WMcObj;
175
176
    /**
177
     * Can be set to contain menu item arrays for sub-levels.
178
     *
179
     * @var string
180
     */
181
    protected $alternativeMenuTempArray = '';
182
183
    /**
184
     * Array key of the parentMenuItem in the parentMenuArr, if this menu is a subMenu.
185
     *
186
     * @var int|null
187
     */
188
    protected $parentMenuArrItemKey;
189
190
    /**
191
     * @var array
192
     */
193
    protected $parentMenuArr;
194
195
    protected const customItemStates = [
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected CUSTOMITEMSTATES).
Loading history...
196
        // IFSUB is TRUE if there exist submenu items to the current item
197
        'IFSUB',
198
        'ACT',
199
        // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
200
        'ACTIFSUB',
201
        // CUR is TRUE if the current page equals the item here!
202
        'CUR',
203
        // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
204
        'CURIFSUB',
205
        'USR',
206
        'SPC',
207
        'USERDEF1',
208
        'USERDEF2'
209
    ];
210
211
    /**
212
     * The initialization of the object. This just sets some internal variables.
213
     *
214
     * @param TemplateService $tmpl The $this->getTypoScriptFrontendController()->tmpl object
215
     * @param PageRepository $sys_page The $this->getTypoScriptFrontendController()->sys_page object
216
     * @param int|string $id A starting point page id. This should probably be blank since the 'entryLevel' value will be used then.
217
     * @param array $conf The TypoScript configuration for the HMENU cObject
218
     * @param int $menuNumber Menu number; 1,2,3. Should probably be 1
219
     * @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")
220
     * @return bool Returns TRUE on success
221
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::HMENU()
222
     */
223
    public function start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix = '')
224
    {
225
        $tsfe = $this->getTypoScriptFrontendController();
226
        $this->conf = $conf;
227
        $this->menuNumber = $menuNumber;
228
        $this->mconf = $conf[$this->menuNumber . $objSuffix . '.'];
229
        $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
230
        // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the PageRepository object
231
        if ($this->conf[$this->menuNumber . $objSuffix] && is_object($tmpl) && is_object($sys_page)) {
232
            $this->tmpl = $tmpl;
233
            $this->sys_page = $sys_page;
234
            // alwaysActivePIDlist initialized:
235
            if (trim($this->conf['alwaysActivePIDlist']) || isset($this->conf['alwaysActivePIDlist.'])) {
236
                if (isset($this->conf['alwaysActivePIDlist.'])) {
237
                    $this->conf['alwaysActivePIDlist'] = $this->parent_cObj->stdWrap(
238
                        $this->conf['alwaysActivePIDlist'],
239
                        $this->conf['alwaysActivePIDlist.']
240
                    );
241
                }
242
                $this->alwaysActivePIDlist = GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
243
            }
244
            // includeNotInMenu initialized:
245
            $includeNotInMenu = $this->conf['includeNotInMenu'];
246
            $includeNotInMenuConf = $this->conf['includeNotInMenu.'] ?? null;
247
            $this->conf['includeNotInMenu'] = is_array($includeNotInMenuConf)
248
                ? $this->parent_cObj->stdWrap($includeNotInMenu, $includeNotInMenuConf)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
249
                : $includeNotInMenu;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
250
            // exclude doktypes that should not be shown in menu (e.g. backend user section)
251
            if ($this->conf['excludeDoktypes']) {
252
                $this->excludedDoktypes = GeneralUtility::intExplode(',', $this->conf['excludeDoktypes']);
253
            }
254
            // EntryLevel
255
            $this->entryLevel = $this->parent_cObj->getKey(
256
                isset($conf['entryLevel.']) ? $this->parent_cObj->stdWrap(
257
                    $conf['entryLevel'],
258
                    $conf['entryLevel.']
259
                ) : $conf['entryLevel'],
260
                $this->tmpl->rootLine
261
            );
262
            // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
263
            // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
264
            if ($id) {
265
                $this->id = (int)$id;
266
            } else {
267
                // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
268
                $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
269
                // Traverse rootline to build MP_array of pages BEFORE the entryLevel
270
                // (MP var for ->id is picked up in the next part of the code...)
271
                foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
272
                    // For overlaid mount points, set the variable right now:
273
                    if ($levelRec['_MP_PARAM'] && $levelRec['_MOUNT_OL']) {
274
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
275
                    }
276
                    // Break when entry level is reached:
277
                    if ($entryLevel >= $this->entryLevel) {
278
                        break;
279
                    }
280
                    // For normal mount points, set the variable for next level.
281
                    if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
282
                        $this->MP_array[] = $levelRec['_MP_PARAM'];
283
                    }
284
                }
285
            }
286
            // Return FALSE if no page ID was set (thus no menu of subpages can be made).
287
            if ($this->id <= 0) {
288
                return false;
289
            }
290
            // Check if page is a mount point, and if so set id and MP_array
291
            // (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...)
292
            $mount_info = $this->sys_page->getMountPointInfo($this->id);
293
            if (is_array($mount_info)) {
294
                $this->MP_array[] = $mount_info['MPvar'];
295
                $this->id = $mount_info['mount_pid'];
296
            }
297
            // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
298
            // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
299
            if ($this->rL_uidRegister === null) {
300
                $this->rL_uidRegister = [];
301
                $rl_MParray = [];
302
                foreach ($this->tmpl->rootLine as $v_rl) {
303
                    // For overlaid mount points, set the variable right now:
304
                    if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
305
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
306
                    }
307
                    // Add to register:
308
                    $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
309
                        (
310
                            !empty($rl_MParray)
311
                            ? ':' . implode(',', $rl_MParray)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
312
                            : ''
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
313
                        );
314
                    // For normal mount points, set the variable for next level.
315
                    if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
316
                        $rl_MParray[] = $v_rl['_MP_PARAM'];
317
                    }
318
                }
319
            }
320
            // Set $directoryLevel so the following evaluation of the nextActive will not return
321
            // an invalid value if .special=directory was set
322
            $directoryLevel = 0;
323
            if ($this->conf['special'] === 'directory') {
324
                $value = isset($this->conf['special.']['value.']) ? $this->parent_cObj->stdWrap(
325
                    $this->conf['special.']['value'],
326
                    $this->conf['special.']['value.']
327
                ) : $this->conf['special.']['value'];
328
                if ($value === '') {
329
                    $value = $tsfe->page['uid'];
330
                }
331
                $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
332
            }
333
            // 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
334
            // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
335
            $startLevel = $directoryLevel ?: $this->entryLevel;
336
            $currentLevel = $startLevel + $this->menuNumber;
337
            if (is_array($this->tmpl->rootLine[$currentLevel])) {
338
                $nextMParray = $this->MP_array;
339
                if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
340
                    // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
341
                    // otherwise automatic expansion will not work
342
                    $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
343
                    if (isset($parentRecord['_MP_PARAM'])) {
344
                        $nextMParray[] = $parentRecord['_MP_PARAM'];
345
                    }
346
                }
347
                // In overlay mode, add next level MPvars as well:
348
                if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
349
                    $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
350
                }
351
                $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
352
                    (
353
                        !empty($nextMParray)
354
                        ? ':' . implode(',', $nextMParray)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
355
                        : ''
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
356
                    );
357
            } else {
358
                $this->nextActive = '';
359
            }
360
            return true;
361
        }
362
        $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
363
        return false;
364
    }
365
366
    /**
367
     * Creates the menu in the internal variables, ready for output.
368
     * Basically this will read the page records needed and fill in the internal $this->menuArr
369
     * Based on a hash of this array and some other variables the $this->result variable will be
370
     * loaded either from cache OR by calling the generate() method of the class to create the menu for real.
371
     */
372
    public function makeMenu()
373
    {
374
        if (!$this->id) {
375
            return;
376
        }
377
378
        // Initializing showAccessRestrictedPages
379
        $SAVED_where_groupAccess = '';
380
        if ($this->mconf['showAccessRestrictedPages']) {
381
            // SAVING where_groupAccess
382
            $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
383
            // Temporarily removing fe_group checking!
384
            $this->sys_page->where_groupAccess = '';
385
        }
386
387
        $menuItems = $this->prepareMenuItems();
388
389
        $c = 0;
390
        $c_b = 0;
391
        $minItems = (int)($this->mconf['minItems'] ?: $this->conf['minItems']);
392
        $maxItems = (int)($this->mconf['maxItems'] ?: $this->conf['maxItems']);
393
        $begin = $this->parent_cObj->calc($this->mconf['begin'] ?: $this->conf['begin']);
394
        $minItemsConf = $this->mconf['minItems.'] ?? $this->conf['minItems.'] ?? null;
395
        $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
396
        $maxItemsConf = $this->mconf['maxItems.'] ?? $this->conf['maxItems.'] ?? null;
397
        $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
398
        $beginConf = $this->mconf['begin.'] ?? $this->conf['begin.'] ?? null;
399
        $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
400
        $banUidArray = $this->getBannedUids();
401
        // Fill in the menuArr with elements that should go into the menu:
402
        $this->menuArr = [];
403
        foreach ($menuItems as $data) {
404
            $isSpacerPage = (int)$data['doktype'] === PageRepository::DOKTYPE_SPACER || $data['ITEM_STATE'] === 'SPC';
405
            // if item is a spacer, $spacer is set
406
            if ($this->filterMenuPages($data, $banUidArray, $isSpacerPage)) {
407
                $c_b++;
408
                // If the beginning item has been reached.
409
                if ($begin <= $c_b) {
410
                    $this->menuArr[$c] = $this->determineOriginalShortcutPage($data);
411
                    $this->menuArr[$c]['isSpacer'] = $isSpacerPage;
412
                    $c++;
413
                    if ($maxItems && $c >= $maxItems) {
414
                        break;
415
                    }
416
                }
417
            }
418
        }
419
        // Fill in fake items, if min-items is set.
420
        if ($minItems) {
421
            while ($c < $minItems) {
422
                $this->menuArr[$c] = [
423
                    'title' => '...',
424
                    'uid' => $this->getTypoScriptFrontendController()->id
425
                ];
426
                $c++;
427
            }
428
        }
429
        //	Passing the menuArr through a user defined function:
430
        if ($this->mconf['itemArrayProcFunc']) {
431
            $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...
432
        }
433
        // Setting number of menu items
434
        $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

434
        $this->getTypoScriptFrontendController()->register['count_menuItems'] = count(/** @scrutinizer ignore-type */ $this->menuArr);
Loading history...
435
        $this->hash = md5(
436
            json_encode($this->menuArr) .
437
            json_encode($this->mconf) .
438
            json_encode($this->tmpl->rootLine) .
439
            json_encode($this->MP_array)
440
        );
441
        // Get the cache timeout:
442
        if ($this->conf['cache_period']) {
443
            $cacheTimeout = $this->conf['cache_period'];
444
        } else {
445
            $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
446
        }
447
        $cache = $this->getCache();
448
        $cachedData = $cache->get($this->hash);
449
        if (!is_array($cachedData)) {
450
            $this->generate();
451
            $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
452
        } else {
453
            $this->result = $cachedData;
454
        }
455
        // End showAccessRestrictedPages
456
        if ($this->mconf['showAccessRestrictedPages']) {
457
            // RESTORING where_groupAccess
458
            $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
459
        }
460
    }
461
462
    /**
463
     * Generates the the menu data.
464
     *
465
     * Subclasses should overwrite this method.
466
     */
467
    public function generate()
468
    {
469
    }
470
471
    /**
472
     * @return string The HTML for the menu
473
     */
474
    public function writeMenu()
475
    {
476
        return '';
477
    }
478
479
    /**
480
     * Gets an array of page rows and removes all, which are not accessible
481
     *
482
     * @param array $pages
483
     * @return array
484
     */
485
    protected function removeInaccessiblePages(array $pages)
486
    {
487
        $banned = $this->getBannedUids();
488
        $filteredPages = [];
489
        foreach ($pages as $aPage) {
490
            if ($this->filterMenuPages($aPage, $banned, (int)$aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
491
                $filteredPages[$aPage['uid']] = $aPage;
492
            }
493
        }
494
        return $filteredPages;
495
    }
496
497
    /**
498
     * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
499
     *
500
     * @return array
501
     */
502
    protected function prepareMenuItems()
503
    {
504
        $menuItems = [];
505
        $alternativeSortingField = trim($this->mconf['alternativeSortingField']) ?: 'sorting';
506
507
        // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
508
        $additionalWhere = $this->mconf['additionalWhere'] ?? '';
509
        if (isset($this->mconf['additionalWhere.'])) {
510
            $additionalWhere = $this->parent_cObj->stdWrap($additionalWhere, $this->mconf['additionalWhere.']);
511
        }
512
        $additionalWhere .= $this->getDoktypeExcludeWhere();
513
514
        // ... only for the FIRST level of a HMENU
515
        if ($this->menuNumber == 1 && $this->conf['special']) {
516
            $value = isset($this->conf['special.']['value.'])
517
                ? $this->parent_cObj->stdWrap($this->conf['special.']['value'], $this->conf['special.']['value.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
518
                : $this->conf['special.']['value'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
519
            switch ($this->conf['special']) {
520
                case 'userfunction':
521
                    $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
522
                    break;
523
                case 'language':
524
                    $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
525
                    break;
526
                case 'directory':
527
                    $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
528
                    break;
529
                case 'list':
530
                    $menuItems = $this->prepareMenuItemsForListMenu($value);
531
                    break;
532
                case 'updated':
533
                    $menuItems = $this->prepareMenuItemsForUpdatedMenu(
534
                        $value,
535
                        $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

535
                        /** @scrutinizer ignore-type */ $this->mconf['alternativeSortingField'] ?: false
Loading history...
536
                    );
537
                    break;
538
                case 'keywords':
539
                    $menuItems = $this->prepareMenuItemsForKeywordsMenu(
540
                        $value,
541
                        $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

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

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