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

FluidTYPO3 / vhs / 11779984761

11 Nov 2024 01:59PM UTC coverage: 72.746% (-0.003%) from 72.749%
11779984761

push

github

NamelessCoder
[TER] 7.0.7

5544 of 7621 relevant lines covered (72.75%)

13.49 hits per line

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

67.45
/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;
42✔
47
    }
48

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

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

226
        return $output;
×
227
    }
228

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

271
        return is_scalar($output) ? (string) $output : '';
18✔
272
    }
273

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

334
        return implode(LF, $html);
18✔
335
    }
336

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

368
        return $html;
18✔
369
    }
370

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

387
        return $pageUid;
30✔
388
    }
389

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

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

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

471
        return $processedPages;
18✔
472
    }
473

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

485
        return $page['title'];
×
486
    }
487

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

515
    public function setOriginal(bool $original): void
516
    {
517
        $this->original = $original;
6✔
518
    }
519

520
    protected function cleanupSubmenuVariables(): void
521
    {
522
        if (!$this->original) {
×
523
            return;
×
524
        }
525
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
×
526
        if (!$viewHelperVariableContainer->exists(AbstractMenuViewHelper::class, 'parentInstance')) {
×
527
            return;
×
528
        }
529
        $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'parentInstance');
×
530
        $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'variables');
×
531
    }
532

533
    /**
534
     * Saves copies of all template variables while rendering
535
     * the menu.
536
     */
537
    public function backupVariables(): void
538
    {
539
        /** @var string $as */
540
        $as = $this->arguments['as'];
18✔
541
        /** @var string $rootLineAs */
542
        $rootLineAs = $this->arguments['rootLineAs'];
18✔
543
        $variableProvider = $this->renderingContext->getVariableProvider();
18✔
544
        $backups = [$as, $rootLineAs];
18✔
545
        foreach ($backups as $var) {
18✔
546
            if ($variableProvider->exists($var)) {
18✔
547
                $this->backupValues[$var] = $variableProvider->get($var);
×
548
                $variableProvider->remove($var);
×
549
            }
550
        }
551
    }
552

553
    /**
554
     * Restores all saved template variables.
555
     */
556
    public function restoreVariables(): void
557
    {
558
        $variableProvider = $this->renderingContext->getVariableProvider();
18✔
559
        if (0 < count($this->backupValues)) {
18✔
560
            foreach ($this->backupValues as $var => $value) {
×
561
                if (!$variableProvider->exists($var)) {
×
562
                    $variableProvider->add($var, $value);
×
563
                }
564
            }
565
        }
566
    }
567

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

595
        return $parentInstance;
12✔
596
    }
597

598
    protected function cleanTemplateVariableContainer(): void
599
    {
600
        if (!$this->renderingContext->getViewHelperVariableContainer()->exists(
×
601
            AbstractMenuViewHelper::class,
×
602
            'variables'
×
603
        )) {
×
604
            return;
×
605
        }
606
        /** @var iterable $storedVariables */
607
        $storedVariables = $this->renderingContext->getViewHelperVariableContainer()->get(
×
608
            AbstractMenuViewHelper::class,
×
609
            'variables'
×
610
        );
×
611
        $variableProvider = $this->renderingContext->getVariableProvider();
×
612
        /** @var iterable $allVariables */
613
        $allVariables = $variableProvider->getAll();
×
614
        foreach ($allVariables as $variableName => $value) {
×
615
            $this->backupValues[$variableName] = $value;
×
616
            $variableProvider->remove($variableName);
×
617
        }
618
        foreach ($storedVariables as $variableName => $value) {
×
619
            /** @var string $variableName */
620
            $variableProvider->add($variableName, $value);
×
621
        }
622
    }
623

624
    public function getMenuArguments(): array
625
    {
626
        if (!is_array($this->arguments)) {
×
627
            return $this->arguments->toArray();
×
628
        }
629
        return $this->arguments;
×
630
    }
631

632
    protected function unsetDeferredVariableStorage(): void
633
    {
634
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
18✔
635
        if ($viewHelperVariableContainer->exists(AbstractMenuViewHelper::class, 'deferredString')) {
18✔
636
            $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'deferredString');
18✔
637
            $viewHelperVariableContainer->remove(AbstractMenuViewHelper::class, 'deferredArray');
18✔
638
        }
639
    }
640

641
    public function getWrappingTagName(): string
642
    {
643
        /** @var string $tagName */
644
        $tagName = $this->arguments['tagName'];
18✔
645
        return $this->isNonWrappingMode() ? 'nav' : $tagName;
18✔
646
    }
647

648
    /**
649
     * Returns TRUE for non-wrapping mode which is triggered
650
     * by setting tagNameChildren to 'a'.
651
     */
652
    public function isNonWrappingMode(): bool
653
    {
654
        /** @var string $tagName */
655
        $tagName = $this->arguments['tagNameChildren'];
18✔
656
        return ('a' === strtolower($tagName));
18✔
657
    }
658

659
    /**
660
     * Returns array of page UIDs from provided pages
661
     *
662
     * @param mixed|null $pages
663
     */
664
    public function processPagesArgument($pages = null): array
665
    {
666
        if (null === $pages) {
42✔
667
            $pages = $this->arguments['pages'];
24✔
668
        }
669
        if ($pages instanceof \Traversable) {
42✔
670
            $pages = iterator_to_array($pages);
×
671
        } elseif (is_string($pages)) {
42✔
672
            $pages = GeneralUtility::trimExplode(',', $pages, true);
30✔
673
        } elseif (is_int($pages)) {
24✔
674
            $pages = (array) $pages;
×
675
        }
676
        if (!is_array($pages)) {
42✔
677
            return [];
12✔
678
        }
679

680
        return $pages;
30✔
681
    }
682
}
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