• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FluidTYPO3 / vhs / 28587840674

02 Jul 2026 11:51AM UTC coverage: 69.967% (+0.08%) from 69.886%
28587840674

push

github

NamelessCoder
[TASK] Collapse unnecessary whitespace breaks

0 of 2 new or added lines in 1 file covered. (0.0%)

4827 of 6899 relevant lines covered (69.97%)

4.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.75
/Classes/ViewHelpers/Menu/AbstractMenuViewHelper.php
1
<?php
2
namespace FluidTYPO3\Vhs\ViewHelpers\Menu;
3

4
/*
5
 * This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10

11
use FluidTYPO3\Vhs\Service\PageService;
12
use FluidTYPO3\Vhs\Traits\PageRecordViewHelperTrait;
13
use FluidTYPO3\Vhs\Traits\TagViewHelperTrait;
14
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
15
use TYPO3\CMS\Core\Utility\GeneralUtility;
16
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
17

18
/**
19
 * Base class for menu rendering ViewHelpers.
20
 */
21
abstract class AbstractMenuViewHelper extends AbstractTagBasedViewHelper
22
{
23
    use PageRecordViewHelperTrait;
24
    use TagViewHelperTrait;
25

26
    /**
27
     * @var string
28
     */
29
    protected $tagName = 'ul';
30

31
    /**
32
     * @var boolean
33
     */
34
    protected $escapeOutput = false;
35

36
    /**
37
     * @var PageService
38
     */
39
    protected $pageService;
40

41
    private bool $original = true;
42
    private array $backupValues = [];
43

44
    public function injectPageService(PageService $pageService): void
45
    {
46
        $this->pageService = $pageService;
14✔
47
    }
48

49
    public function initializeArguments(): void
50
    {
51
        parent::initializeArguments();
12✔
52
        $this->registerUniversalTagAttributes();
12✔
53
        $this->registerPageRecordArguments();
12✔
54
        $this->registerArgument('tagName', 'string', 'Tag name to use for enclosing container', false, 'ul');
12✔
55
        $this->registerArgument(
12✔
56
            'tagNameChildren',
12✔
57
            'string',
12✔
58
            'Tag name to use for child nodes surrounding links. If set to "a" enables non-wrapping mode.',
12✔
59
            false,
12✔
60
            'li'
12✔
61
        );
12✔
62
        $this->registerArgument('entryLevel', 'integer', 'Optional entryLevel TS equivalent of the menu', false, 0);
12✔
63
        $this->registerArgument(
12✔
64
            'levels',
12✔
65
            'integer',
12✔
66
            'Number of levels to render - setting this to a number higher than 1 (one) will expand menu ' .
12✔
67
            'items that are active, to a depth of $levels starting from $entryLevel',
12✔
68
            false,
12✔
69
            1
12✔
70
        );
12✔
71
        $this->registerArgument(
12✔
72
            'expandAll',
12✔
73
            'boolean',
12✔
74
            'If TRUE and $levels > 1 then expands all (not just the active) menu items which have submenus',
12✔
75
            false,
12✔
76
            false
12✔
77
        );
12✔
78
        $this->registerArgument('classFirst', 'string', 'Optional class name for the first menu elment', false, '');
12✔
79
        $this->registerArgument('classLast', 'string', 'Optional class name for the last menu elment', false, '');
12✔
80
        $this->registerArgument('classActive', 'string', 'Optional class name to add to active links', false, 'active');
12✔
81
        $this->registerArgument(
12✔
82
            'classCurrent',
12✔
83
            'string',
12✔
84
            'Optional class name to add to current link',
12✔
85
            false,
12✔
86
            'current'
12✔
87
        );
12✔
88
        $this->registerArgument(
12✔
89
            'substElementUid',
12✔
90
            'boolean',
12✔
91
            'Optional parameter for wrapping the link with the uid of the page',
12✔
92
            false,
12✔
93
            false
12✔
94
        );
12✔
95
        $this->registerArgument(
12✔
96
            'showHiddenInMenu',
12✔
97
            'boolean',
12✔
98
            'Include pages that are set to be hidden in menus',
12✔
99
            false,
12✔
100
            false
12✔
101
        );
12✔
102
        $this->registerArgument('showCurrent', 'boolean', 'If FALSE, does not display the current page', false, true);
12✔
103
        $this->registerArgument(
12✔
104
            'linkCurrent',
12✔
105
            'boolean',
12✔
106
            'If FALSE, does not wrap the current page in a link',
12✔
107
            false,
12✔
108
            true
12✔
109
        );
12✔
110
        $this->registerArgument(
12✔
111
            'linkActive',
12✔
112
            'boolean',
12✔
113
            'If FALSE, does not wrap with links the titles of pages that are active in the rootline',
12✔
114
            false,
12✔
115
            true
12✔
116
        );
12✔
117
        $this->registerArgument(
12✔
118
            'titleFields',
12✔
119
            'string',
12✔
120
            'CSV list of fields to use as link label - default is "nav_title,title", change to for example ' .
12✔
121
            '"tx_myext_somefield,subtitle,nav_title,title". The first field that contains text will be used. ' .
12✔
122
            'Field value resolved AFTER page field overlays.',
12✔
123
            false,
12✔
124
            'nav_title,title'
12✔
125
        );
12✔
126
        $this->registerArgument(
12✔
127
            'includeAnchorTitle',
12✔
128
            'boolean',
12✔
129
            'If TRUE, includes the page title as title attribute on the anchor.',
12✔
130
            false,
12✔
131
            true
12✔
132
        );
12✔
133
        $this->registerArgument(
12✔
134
            'includeSpacers',
12✔
135
            'boolean',
12✔
136
            'Wether or not to include menu spacers in the page select query',
12✔
137
            false,
12✔
138
            false
12✔
139
        );
12✔
140
        $this->registerArgument(
12✔
141
            'deferred',
12✔
142
            'boolean',
12✔
143
            'If TRUE, does not output the tag content UNLESS a v:page.menu.deferred child ViewHelper is both used ' .
12✔
144
            'and triggered. This allows you to create advanced conditions while still using automatic rendering',
12✔
145
            false,
12✔
146
            false
12✔
147
        );
12✔
148
        $this->registerArgument(
12✔
149
            'as',
12✔
150
            'string',
12✔
151
            'If used, stores the menu pages as an array in a variable named after this value and renders the tag ' .
12✔
152
            'content. If the tag content is empty automatic rendering is triggered.',
12✔
153
            false,
12✔
154
            'menu'
12✔
155
        );
12✔
156
        $this->registerArgument(
12✔
157
            'rootLineAs',
12✔
158
            'string',
12✔
159
            'If used, stores the menu root line as an array in a variable named according to this value and renders ' .
12✔
160
            'the tag content - which means automatic rendering is disabled if this attribute is used',
12✔
161
            false,
12✔
162
            'rootLine'
12✔
163
        );
12✔
164
        $this->registerArgument(
12✔
165
            'excludePages',
12✔
166
            'mixed',
12✔
167
            'Page UIDs to exclude from the menu. Can be CSV, array or an object implementing Traversable.',
12✔
168
            false,
12✔
169
            ''
12✔
170
        );
12✔
171
        $this->registerArgument(
12✔
172
            'forceAbsoluteUrl',
12✔
173
            'boolean',
12✔
174
            'If TRUE, the menu will be rendered with absolute URLs',
12✔
175
            false,
12✔
176
            false
12✔
177
        );
12✔
178
        $this->registerArgument(
12✔
179
            'doktypes',
12✔
180
            'mixed',
12✔
181
            'DEPRECATED: Please use typical doktypes for starting points like shortcuts.',
12✔
182
            false,
12✔
183
            ''
12✔
184
        );
12✔
185
        $this->registerArgument(
12✔
186
            'divider',
12✔
187
            'string',
12✔
188
            'Optional divider to insert between each menu item. Note that this does not mix well with automatic ' .
12✔
189
            'rendering due to the use of an ul > li structure'
12✔
190
        );
12✔
191
    }
192

193
    public function render(): string
194
    {
195
        /** @var int|null $entryLevel */
196
        $entryLevel = $this->arguments['entryLevel'];
×
197
        /** @var int|null $pageUid */
198
        $pageUid = $this->arguments['pageUid'];
×
199
        $pageUid = $pageUid > 0 ? (int) $pageUid : null;
×
200
        $pages = $this->getMenu($pageUid, (int) $entryLevel);
×
201
        $menu = $this->parseMenu($pages);
×
202
        $rootLine = $this->pageService->getRootLine(
×
203
            $pageUid,
×
204
            (bool) ($this->arguments['reverse'] ?? false)
×
205
        );
×
206
        $this->cleanupSubmenuVariables();
×
207
        $this->cleanTemplateVariableContainer();
×
208
        $this->backupVariables();
×
209
        $variableProvider = $this->renderingContext->getVariableProvider();
×
210
        /** @var string $as */
211
        $as = $this->arguments['as'];
×
212
        /** @var string $rootLineAs */
213
        $rootLineAs = $this->arguments['rootLineAs'];
×
214
        $variableProvider->add($as, $menu);
×
215
        $variableProvider->add($rootLineAs, $rootLine);
×
216
        $this->initalizeSubmenuVariables();
×
217
        $output = $this->renderContent($menu);
×
218
        $this->cleanupSubmenuVariables();
×
219
        $variableProvider->remove($as);
×
220
        $variableProvider->remove($rootLineAs);
×
221
        $this->restoreVariables();
×
222

223
        return $output;
×
224
    }
225

226
    /**
227
     * Renders the tag's content or if omitted auto
228
     * renders the menu for the provided arguments.
229
     */
230
    public function renderContent(array $menu): string
231
    {
232
        $deferredRendering = (bool) $this->arguments['deferred'];
6✔
233
        if (0 === count($menu) && !$deferredRendering) {
6✔
234
            return '';
×
235
        }
236
        if ($deferredRendering) {
6✔
237
            $tagContent = $this->autoRender($menu);
×
238
            $this->tag->setContent($tagContent);
×
239
            $deferredContent = $this->tag->render();
×
240
            $this->renderingContext->getViewHelperVariableContainer()->addOrUpdate(
×
241
                'FluidTYPO3\Vhs\ViewHelpers\Menu\AbstractMenuViewHelper',
×
242
                'deferredString',
×
243
                $deferredContent
×
244
            );
×
245
            $this->renderingContext->getViewHelperVariableContainer()->addOrUpdate(
×
246
                'FluidTYPO3\Vhs\ViewHelpers\Menu\AbstractMenuViewHelper',
×
247
                'deferredArray',
×
248
                $menu
×
249
            );
×
250
            $output = $this->renderChildren();
×
251
            $this->unsetDeferredVariableStorage();
×
252
        } else {
253
            $content = $this->renderChildren();
6✔
254
            if (!is_scalar($content)) {
6✔
255
                $content = '';
6✔
256
            } else {
257
                $content = (string) $content;
×
258
            }
259
            if (0 < mb_strlen(trim($content))) {
6✔
260
                $output = $content;
×
261
            } elseif ($this->arguments['hideIfEmpty']) {
6✔
262
                $output = '';
×
263
            } else {
264
                $output = $this->renderTag($this->getWrappingTagName(), $this->autoRender($menu));
6✔
265
            }
266
        }
267

268
        return is_scalar($output) ? (string) $output : '';
6✔
269
    }
270

271
    protected function autoRender(array $menu, int $level = 1): string
272
    {
273
        /** @var string $tagName */
274
        $tagName = $this->arguments['tagNameChildren'];
6✔
275
        $this->tag->setTagName($this->getWrappingTagName());
6✔
276
        $html = [];
6✔
277
        /** @var int $levels */
278
        $levels = $this->arguments['levels'];
6✔
279
        $levels = (int) $levels;
6✔
280
        $showCurrent = (bool) $this->arguments['showCurrent'];
6✔
281
        $expandAll = (bool) $this->arguments['expandAll'];
6✔
282
        $itemsRendered = 0;
6✔
283
        $numberOfItems = count($menu);
6✔
284
        foreach ($menu as $page) {
6✔
285
            if ($page['current'] && !$showCurrent) {
6✔
286
                continue;
×
287
            }
288
            $class = (trim($page['class']) !== '') ? ' class="' . trim($page['class']) . '"' : '';
6✔
289
            $elementId = ($this->arguments['substElementUid']) ? ' id="elem_' . $page['uid'] . '"' : '';
6✔
290
            if (!$this->isNonWrappingMode()) {
6✔
291
                $html[] = '<' . $tagName . $elementId . $class . '>';
6✔
292
            }
293
            $html[] = $this->renderItemLink($page);
6✔
294
            if (($page['active'] || $expandAll) && $page['hasSubPages'] && $level < $levels) {
6✔
295
                $subPages = $this->getMenu($page['uid']);
×
296
                $subMenu = $this->parseMenu($subPages);
×
297
                if (0 < count($subMenu)) {
×
298
                    /** @var string|null $className */
299
                    $className = $this->arguments['class'];
×
300
                    $renderedSubMenu = $this->autoRender($subMenu, $level + 1);
×
301
                    /** @var string|null $parentTagId */
302
                    $parentTagId = $this->tag->getAttribute('id') ?? $this->arguments['id'] ?? null;
×
303
                    if (!empty($parentTagId)) {
×
304
                        $this->tag->addAttribute('id', $parentTagId . '-lvl-' . $level);
×
305
                    }
306
                    $this->tag->setTagName($this->getWrappingTagName());
×
307
                    $this->tag->setContent($renderedSubMenu);
×
308
                    $this->tag->addAttribute(
×
309
                        'class',
×
310
                        (!empty($className) ? $className . ' lvl-' : 'lvl-') . $level
×
311
                    );
×
312
                    $html[] = $this->tag->render();
×
313
                    if (!empty($parentTagId)) {
×
314
                        $this->tag->addAttribute('id', $parentTagId);
×
315
                    }
316
                }
317
            }
318
            if (!$this->isNonWrappingMode()) {
6✔
319
                $html[] = '</' . $tagName . '>';
6✔
320
            }
321
            $itemsRendered++;
6✔
322
            if (isset($this->arguments['divider']) && $itemsRendered < $numberOfItems) {
6✔
323
                $divider = $this->arguments['divider'];
×
324
                if (!$this->isNonWrappingMode()) {
×
325
                    $html[] = '<' . $tagName . '>' . $divider . '</' . $tagName . '>';
×
326
                } else {
327
                    $html[] = $divider;
×
328
                }
329
            }
330
        }
331

332
        return implode(PHP_EOL, $html);
6✔
333
    }
334

335
    protected function renderItemLink(array $page): string
336
    {
337
        $isSpacer = $page['doktype'] === PageRepository::DOKTYPE_SPACER;
6✔
338
        $isCurrent = (bool) $page['current'];
6✔
339
        $isActive = (bool) $page['active'];
6✔
340
        $linkCurrent = (bool) $this->arguments['linkCurrent'];
6✔
341
        $linkActive = (bool) $this->arguments['linkActive'];
6✔
342
        $includeAnchorTitle = (bool) $this->arguments['includeAnchorTitle'];
6✔
343
        $target = (!empty($page['target'])) ? ' target="' . $page['target'] . '"' : '';
6✔
344
        $class = (trim($page['class']) !== '') ? ' class="' . trim($page['class']) . '"' : '';
6✔
345
        if ($isSpacer || ($isCurrent && !$linkCurrent) || ($isActive && !$linkActive)) {
6✔
346
            $html = htmlspecialchars($page['linktext']);
×
347
        } elseif ($includeAnchorTitle) {
6✔
348
            $html = sprintf(
6✔
349
                '<a href="%s" title="%s"%s%s>%s</a>',
6✔
350
                $page['link'],
6✔
351
                htmlspecialchars($page['title']),
6✔
352
                $class,
6✔
353
                $target,
6✔
354
                htmlspecialchars($page['linktext'])
6✔
355
            );
6✔
356
        } else {
357
            $html = sprintf(
×
358
                '<a href="%s"%s%s>%s</a>',
×
359
                $page['link'],
×
360
                $class,
×
361
                $target,
×
362
                htmlspecialchars($page['linktext'])
×
363
            );
×
364
        }
365

366
        return $html;
6✔
367
    }
368

369
    protected function determineParentPageUid(?int $pageUid = null, ?int $entryLevel = 0): ?int
370
    {
371
        $rootLineData = $this->pageService->getRootLine();
10✔
372
        if (null === $pageUid) {
10✔
373
            if (null !== $entryLevel) {
×
374
                if ($entryLevel < 0) {
×
375
                    $entryLevel = count($rootLineData) - 1 + $entryLevel;
×
376
                }
377
                if (is_array($rootLineData[$entryLevel] ?? null)) {
×
378
                    $pageUid = $rootLineData[$entryLevel]['uid'] ?? null;
×
379
                }
380
            } else {
381
                $pageUid = $GLOBALS['TSFE']->id;
×
382
            }
383
        }
384

385
        return $pageUid;
10✔
386
    }
387

388
    public function getMenu(?int $pageUid = null, ?int $entryLevel = 0): array
389
    {
390
        $pageUid = $this->determineParentPageUid($pageUid, $entryLevel);
10✔
391
        if ($pageUid === null) {
10✔
392
            return [];
×
393
        }
394
        $showHiddenInMenu = (bool) $this->arguments['showHiddenInMenu'];
10✔
395
        $showAccessProtected = (bool) $this->arguments['showAccessProtected'];
10✔
396
        $includeSpacers = (bool) $this->arguments['includeSpacers'];
10✔
397
        $excludePages = $this->processPagesArgument($this->arguments['excludePages']);
10✔
398

399
        return $this->pageService->getMenu(
10✔
400
            $pageUid,
10✔
401
            $excludePages,
10✔
402
            $showHiddenInMenu,
10✔
403
            $includeSpacers,
10✔
404
            $showAccessProtected
10✔
405
        );
10✔
406
    }
407

408
    public function parseMenu(array $pages): array
409
    {
410
        $count = 0;
6✔
411
        $total = count($pages);
6✔
412
        $processedPages = [];
6✔
413
        foreach ($pages as $index => $page) {
6✔
414
            if (!is_array($page) || !isset($page['uid'])) {
6✔
415
                continue;
×
416
            }
417
            $count++;
6✔
418
            $class = [];
6✔
419
            $originalPageUid = $page['uid'];
6✔
420
            $showAccessProtected = (bool) $this->arguments['showAccessProtected'];
6✔
421
            if ($showAccessProtected) {
6✔
422
                $pages[$index]['accessProtected'] = $this->pageService->isAccessProtected($page);
×
423
                if ($pages[$index]['accessProtected']) {
×
424
                    $class[] = $this->arguments['classAccessProtected'];
×
425
                }
426
                $pages[$index]['accessGranted'] = $this->pageService->isAccessGranted($page);
×
427
                if ($pages[$index]['accessGranted'] && $this->pageService->isAccessProtected($page)) {
×
428
                    $class[] = $this->arguments['classAccessGranted'];
×
429
                }
430
            }
431
            $targetPage = $this->pageService->getShortcutTargetPage($page);
6✔
432
            if ($targetPage !== null) {
6✔
433
                if ($this->pageService->shouldUseShortcutTarget($this->arguments)) {
2✔
434
                    $pages[$index] = $targetPage;
×
435
                }
436
                if ($this->pageService->shouldUseShortcutUid($this->arguments)) {
2✔
437
                    $pages[$index]['uid'] = $targetPage['uid'];
×
438
                }
439
            }
440
            if ($this->pageService->isActive($originalPageUid)) {
6✔
441
                $pages[$index]['active'] = true;
6✔
442
                $class[] = $this->arguments['classActive'];
6✔
443
            } else {
444
                $pages[$index]['active'] = false;
×
445
            }
446
            if ($this->pageService->isCurrent($targetPage['uid'] ?? $page['uid'])) {
6✔
447
                $pages[$index]['current'] = true;
4✔
448
                $class[] = $this->arguments['classCurrent'];
4✔
449
            } else {
450
                 $pages[$index]['current'] = false;
2✔
451
            }
452
            $pages[$index]['hasSubPages'] = false;
6✔
453
            if (0 < count($this->getMenu($originalPageUid))) {
6✔
454
                $pages[$index]['hasSubPages'] = true;
6✔
455
            }
456
            if (1 === $count) {
6✔
457
                $class[] = $this->arguments['classFirst'];
6✔
458
            }
459
            if ($count === $total) {
6✔
460
                $class[] = $this->arguments['classLast'];
6✔
461
            }
462
            $pages[$index]['class'] = implode(' ', $class);
6✔
463
            $pages[$index]['linktext'] = $this->getItemTitle($pages[$index]);
6✔
464
            $forceAbsoluteUrl = (bool) $this->arguments['forceAbsoluteUrl'];
6✔
465
            $pages[$index]['link'] = $this->pageService->getItemLink($pages[$index], $forceAbsoluteUrl);
6✔
466
            $processedPages[$index] = $pages[$index];
6✔
467
        }
468

469
        return $processedPages;
6✔
470
    }
471

472
    protected function getItemTitle(array $page): string
473
    {
474
        /** @var string $titleFieldsArgument */
475
        $titleFieldsArgument = $this->arguments['titleFields'];
6✔
476
        $titleFieldList = GeneralUtility::trimExplode(',', $titleFieldsArgument);
6✔
477
        foreach ($titleFieldList as $titleFieldName) {
6✔
478
            if (!empty($page[$titleFieldName])) {
6✔
479
                return $page[$titleFieldName];
6✔
480
            }
481
        }
482

483
        return $page['title'];
×
484
    }
485

486
    /**
487
     * Initialize variables used by the submenu instance recycler. Variables set here
488
     * may be read by the Page / Menu / Sub ViewHelper which then automatically repeats
489
     * rendering using the exact same arguments but with a new page UID as starting page.
490
     * Note that the submenu VieWHelper is only capable of recycling one type of menu at
491
     * a time - for example, a List menu nested inside a regular Menu ViewHelper will
492
     * simply start another menu rendering completely separate from the parent menu.
493
     */
494
    protected function initalizeSubmenuVariables(): void
495
    {
496
        if (!$this->original) {
×
497
            return;
×
498
        }
499
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
×
500
        $variables = $this->renderingContext->getVariableProvider()->getAll();
×
NEW
501
        $viewHelperVariableContainer->addOrUpdate(AbstractMenuViewHelper::class, 'parentInstance', $this);
×
NEW
502
        $viewHelperVariableContainer->addOrUpdate(AbstractMenuViewHelper::class, 'variables', $variables);
×
503
    }
504

505
    public function setOriginal(bool $original): void
506
    {
507
        $this->original = $original;
2✔
508
    }
509

510
    protected function cleanupSubmenuVariables(): void
511
    {
512
        if (!$this->original) {
×
513
            return;
×
514
        }
515
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
×
516
        if (!$viewHelperVariableContainer->exists(AbstractMenuViewHelper::class, 'parentInstance')) {
×
517
            return;
×
518
        }
519
        $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'parentInstance');
×
520
        $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'variables');
×
521
    }
522

523
    /**
524
     * Saves copies of all template variables while rendering
525
     * the menu.
526
     */
527
    public function backupVariables(): void
528
    {
529
        /** @var string $as */
530
        $as = $this->arguments['as'];
6✔
531
        /** @var string $rootLineAs */
532
        $rootLineAs = $this->arguments['rootLineAs'];
6✔
533
        $variableProvider = $this->renderingContext->getVariableProvider();
6✔
534
        $backups = [$as, $rootLineAs];
6✔
535
        foreach ($backups as $var) {
6✔
536
            if ($variableProvider->exists($var)) {
6✔
537
                $this->backupValues[$var] = $variableProvider->get($var);
×
538
                $variableProvider->remove($var);
×
539
            }
540
        }
541
    }
542

543
    /**
544
     * Restores all saved template variables.
545
     */
546
    public function restoreVariables(): void
547
    {
548
        $variableProvider = $this->renderingContext->getVariableProvider();
6✔
549
        if (0 < count($this->backupValues)) {
6✔
550
            foreach ($this->backupValues as $var => $value) {
×
551
                if (!$variableProvider->exists($var)) {
×
552
                    $variableProvider->add($var, $value);
×
553
                }
554
            }
555
        }
556
    }
557

558
    /**
559
     * Retrieves a stored, if any, parent instance of a menu. Although only implemented by
560
     * the Page / Menu / Sub ViewHelper, placing this method in this abstract class instead
561
     * will allow custom menu ViewHelpers to work as sub menu ViewHelpers without being
562
     * forced to implement their own variable retrieval or subclass Page / Menu / Sub.
563
     * Returns NULL if no parent exists.
564
     *
565
     * @param integer $pageUid UID of page that's the new parent page, overridden in arguments of cloned and
566
     *                         recycled menu ViewHelper instance.
567
     */
568
    protected function retrieveReconfiguredParentMenuInstance(int $pageUid): ?self
569
    {
570
        if (!$this->renderingContext->getViewHelperVariableContainer()->exists(
6✔
571
            AbstractMenuViewHelper::class,
6✔
572
            'parentInstance'
6✔
573
        )) {
6✔
574
            return null;
2✔
575
        }
576
        /** @var AbstractMenuViewHelper $parentInstance */
577
        $parentInstance = $this->renderingContext->getViewHelperVariableContainer()->get(
4✔
578
            AbstractMenuViewHelper::class,
4✔
579
            'parentInstance'
4✔
580
        );
4✔
581
        $arguments = $parentInstance->getMenuArguments();
4✔
582
        $arguments['pageUid'] = $pageUid;
4✔
583
        $parentInstance->setArguments($arguments);
4✔
584

585
        return $parentInstance;
4✔
586
    }
587

588
    protected function cleanTemplateVariableContainer(): void
589
    {
590
        if (!$this->renderingContext->getViewHelperVariableContainer()->exists(
×
591
            AbstractMenuViewHelper::class,
×
592
            'variables'
×
593
        )) {
×
594
            return;
×
595
        }
596
        /** @var iterable $storedVariables */
597
        $storedVariables = $this->renderingContext->getViewHelperVariableContainer()->get(
×
598
            AbstractMenuViewHelper::class,
×
599
            'variables'
×
600
        );
×
601
        $variableProvider = $this->renderingContext->getVariableProvider();
×
602
        /** @var iterable $allVariables */
603
        $allVariables = $variableProvider->getAll();
×
604
        foreach ($allVariables as $variableName => $value) {
×
605
            $this->backupValues[$variableName] = $value;
×
606
            $variableProvider->remove($variableName);
×
607
        }
608
        foreach ($storedVariables as $variableName => $value) {
×
609
            /** @var string $variableName */
610
            $variableProvider->add($variableName, $value);
×
611
        }
612
    }
613

614
    public function getMenuArguments(): array
615
    {
616
        if (!is_array($this->arguments)) {
×
617
            return $this->arguments->toArray();
×
618
        }
619
        return $this->arguments;
×
620
    }
621

622
    protected function unsetDeferredVariableStorage(): void
623
    {
624
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
2✔
625
        if ($viewHelperVariableContainer->exists(AbstractMenuViewHelper::class, 'deferredString')) {
2✔
626
            $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'deferredString');
2✔
627
            $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'deferredArray');
2✔
628
        }
629
    }
630

631
    public function getWrappingTagName(): string
632
    {
633
        /** @var string $tagName */
634
        $tagName = $this->arguments['tagName'];
6✔
635
        return $this->isNonWrappingMode() ? 'nav' : $tagName;
6✔
636
    }
637

638
    /**
639
     * Returns TRUE for non-wrapping mode which is triggered
640
     * by setting tagNameChildren to 'a'.
641
     */
642
    public function isNonWrappingMode(): bool
643
    {
644
        /** @var string $tagName */
645
        $tagName = $this->arguments['tagNameChildren'];
6✔
646
        return ('a' === strtolower($tagName));
6✔
647
    }
648

649
    /**
650
     * Returns array of page UIDs from provided pages
651
     *
652
     * @param mixed|null $pages
653
     */
654
    public function processPagesArgument($pages = null): array
655
    {
656
        if (null === $pages) {
14✔
657
            $pages = $this->arguments['pages'];
8✔
658
        }
659
        if ($pages instanceof \Traversable) {
14✔
660
            $pages = iterator_to_array($pages);
×
661
        } elseif (is_string($pages)) {
14✔
662
            $pages = GeneralUtility::trimExplode(',', $pages, true);
10✔
663
        } elseif (is_int($pages)) {
8✔
664
            $pages = (array) $pages;
×
665
        }
666
        if (!is_array($pages)) {
14✔
667
            return [];
4✔
668
        }
669

670
        return $pages;
10✔
671
    }
672
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc