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

FluidTYPO3 / vhs / 23546129789

25 Mar 2026 02:25PM UTC coverage: 72.022% (-0.02%) from 72.043%
23546129789

push

github

NamelessCoder
[TER] 7.2.1

5648 of 7842 relevant lines covered (72.02%)

19.99 hits per line

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

87.88
/Classes/Service/PageService.php
1
<?php
2

3
namespace FluidTYPO3\Vhs\Service;
4

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

12
use Psr\Http\Message\ServerRequestInterface;
13
use TYPO3\CMS\Core\Context\Context;
14
use TYPO3\CMS\Core\Context\LanguageAspect;
15
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
16
use TYPO3\CMS\Core\SingletonInterface;
17
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
18
use TYPO3\CMS\Core\Utility\GeneralUtility;
19
use TYPO3\CMS\Core\Utility\RootlineUtility;
20
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
21

22
/**
23
 * Page Service
24
 *
25
 * Wrapper service for \TYPO3\CMS\Frontend\Page\PageRepository including static caches for
26
 * menus, rootlines, pages and page overlays to be implemented in
27
 * viewhelpers by replacing calls to \TYPO3\CMS\Frontend\Page\PageRepository::getMenu()
28
 * and the like.
29
 */
30
class PageService implements SingletonInterface
31
{
32
    const DOKTYPE_MOVE_TO_PLACEHOLDER = 0;
33

34
    protected static array $cachedPages = [];
35
    protected static array $cachedMenus = [];
36

37
    public function getMenu(
38
        int $pageUid,
39
        array $excludePages = [],
40
        bool $includeNotInMenu = false,
41
        bool $includeMenuSeparator = false,
42
        bool $disableGroupAccessCheck = false
43
    ): array {
44
        $pageRepository = $this->getPageRepository();
7✔
45
        $pageConstraints = $this->getPageConstraints($excludePages, $includeNotInMenu, $includeMenuSeparator);
7✔
46
        $cacheKey = md5($pageUid . $pageConstraints . (integer) $disableGroupAccessCheck);
7✔
47
        if (!isset(static::$cachedMenus[$cacheKey])) {
7✔
48
            if ($disableGroupAccessCheck
7✔
49
                && version_compare(VersionNumberUtility::getCurrentTypo3Version(), '12.1', '<=')
7✔
50
            ) {
51
                $pageRepository->where_groupAccess = '';
×
52
            }
53

54
            static::$cachedMenus[$cacheKey] = array_filter(
7✔
55
                $pageRepository->getMenu($pageUid, '*', 'sorting', $pageConstraints, true, $disableGroupAccessCheck),
7✔
56
                function ($page) use ($includeNotInMenu) {
7✔
57
                    return (!($page['nav_hide'] ?? false) || $includeNotInMenu)
7✔
58
                        && !$this->hidePageForLanguageUid($page);
7✔
59
                }
7✔
60
            );
7✔
61
        }
62

63
        return static::$cachedMenus[$cacheKey];
7✔
64
    }
65

66
    public function getPage(int $pageUid, bool $disableGroupAccessCheck = false): array
67
    {
68
        $cacheKey = md5($pageUid . (integer) $disableGroupAccessCheck);
7✔
69
        if (!isset(static::$cachedPages[$cacheKey])) {
7✔
70
            static::$cachedPages[$cacheKey] = $this->getPageRepository()->getPage($pageUid, $disableGroupAccessCheck);
7✔
71
        }
72

73
        return static::$cachedPages[$cacheKey];
7✔
74
    }
75

76
    public function getRootLine(
77
        ?int $pageUid = null,
78
        bool $reverse = false
79
    ): array {
80
        if (null === $pageUid) {
28✔
81
            if (isset($GLOBALS['TSFE'])) {
14✔
82
                $pageUid = $GLOBALS['TSFE']->id;
14✔
83
            } else {
84
                $pageUid = $this->getRequest()->getQueryParams()['id'] ?? null;
×
85
            }
86
        }
87

88
        if (!$pageUid) {
28✔
89
            throw new \UnexpectedValueException('PageService::getRootLine requires a page UID', 1774448248);
×
90
        }
91

92
        /** @var RootlineUtility $rootLineUtility */
93
        $rootLineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageUid);
28✔
94
        $rootline = $rootLineUtility->get();
28✔
95
        if ($reverse) {
28✔
96
            $rootline = array_reverse($rootline);
14✔
97
        }
98
        return $rootline;
28✔
99
    }
100

101
    protected function getPageConstraints(
102
        array $excludePages = [],
103
        bool $includeNotInMenu = false,
104
        bool $includeMenuSeparator = false
105
    ): string {
106
        $constraints = [];
7✔
107

108
        $types = [
7✔
109
            PageRepository::DOKTYPE_BE_USER_SECTION,
7✔
110
            PageRepository::DOKTYPE_SYSFOLDER
7✔
111
        ];
7✔
112

113
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '12.4', '<=')) {
7✔
114
            $types[] = PageRepository::DOKTYPE_RECYCLER;
3✔
115
        }
116

117
        $constraints[] = 'doktype NOT IN (' . implode(',', $types) . ')';
7✔
118

119
        if ($includeNotInMenu === false) {
7✔
120
            $constraints[] = 'nav_hide = 0';
7✔
121
        }
122

123
        if ($includeMenuSeparator === false) {
7✔
124
            $constraints[] = 'doktype != ' . PageRepository::DOKTYPE_SPACER;
7✔
125
        }
126

127
        if (0 < count($excludePages)) {
7✔
128
            $constraints[] = 'uid NOT IN (' . implode(',', $excludePages) . ')';
×
129
        }
130

131
        return 'AND ' . implode(' AND ', $constraints);
7✔
132
    }
133

134
    /**
135
     * @param array|integer|null $page
136
     */
137
    public function hidePageForLanguageUid($page = null, int $languageUid = -1, bool $normalWhenNoLanguage = true): bool
138
    {
139
        if (is_array($page)) {
7✔
140
            $pageUid = $page['uid'];
7✔
141
            $pageRecord = $page;
7✔
142
        } else {
143
            $pageUid = (0 === (integer) $page) ? $GLOBALS['TSFE']->id : (integer) $page;
×
144
            $pageRecord = $this->getPage($pageUid);
×
145
        }
146
        if (-1 === $languageUid) {
7✔
147
            if (class_exists(LanguageAspect::class)) {
7✔
148
                /** @var Context $context */
149
                $context = GeneralUtility::makeInstance(Context::class);
7✔
150
                /** @var LanguageAspect $languageAspect */
151
                $languageAspect = $context->getAspect('language');
7✔
152
                $languageUid = $languageAspect->getId();
7✔
153
            } else {
154
                $languageUid = $GLOBALS['TSFE']->sys_language_uid;
×
155
            }
156
        }
157

158
        $l18nCfg = $pageRecord['l18n_cfg'] ?? 0;
7✔
159
        if (class_exists(PageTranslationVisibility::class)) {
7✔
160
            /** @var PageTranslationVisibility $visibilityBitSet */
161
            $visibilityBitSet = GeneralUtility::makeInstance(
6✔
162
                PageTranslationVisibility::class,
6✔
163
                $l18nCfg
6✔
164
            );
6✔
165
            $hideIfNotTranslated = $visibilityBitSet->shouldHideTranslationIfNoTranslatedRecordExists();
6✔
166
            $hideIfDefaultLanguage = $visibilityBitSet->shouldBeHiddenInDefaultLanguage();
6✔
167
        } else {
168
            $hideIfNotTranslated = (boolean) GeneralUtility::hideIfNotTranslated($l18nCfg);
1✔
169
            $hideIfDefaultLanguage = (boolean) GeneralUtility::hideIfDefaultLanguage($l18nCfg);
1✔
170
        }
171

172
        $pageOverlay = [];
7✔
173
        if (0 !== $languageUid) {
7✔
174
            $pageOverlay = $this->getPageRepository()->getPageOverlay($pageUid, $languageUid);
×
175
        }
176
        $translationAvailable = (0 !== count($pageOverlay));
7✔
177

178
        return
7✔
179
            ($hideIfNotTranslated && (0 !== $languageUid) && !$translationAvailable) ||
7✔
180
            ($hideIfDefaultLanguage && ((0 === $languageUid) || !$translationAvailable)) ||
7✔
181
            (!$normalWhenNoLanguage && (0 !== $languageUid) && !$translationAvailable);
7✔
182
    }
183

184
    public function getItemLink(array $page, bool $forceAbsoluteUrl = false): string
185
    {
186
        $parameter = $page['uid'];
14✔
187
        if ((integer) $page['doktype'] === PageRepository::DOKTYPE_LINK) {
14✔
188
            $redirectTo = $page['url'] ?? '';
7✔
189
            if (!empty($redirectTo)) {
7✔
190
                $uI = parse_url($redirectTo);
×
191
                // If relative path, prefix Site URL
192
                // If it's a valid email without protocol, add "mailto:"
193
                if (!($uI['scheme'] ?? false)) {
×
194
                    if (GeneralUtility::validEmail($redirectTo)) {
×
195
                        $redirectTo = 'mailto:' . $redirectTo;
×
196
                    } elseif ($redirectTo[0] !== '/') {
×
197
                        $redirectTo = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . $redirectTo;
×
198
                    }
199
                }
200
                $parameter = $redirectTo;
×
201
            }
202
        }
203
        $config = [
14✔
204
            'parameter' => $parameter,
14✔
205
            'returnLast' => 'url',
14✔
206
            'additionalParams' => '',
14✔
207
            'forceAbsoluteUrl' => $forceAbsoluteUrl,
14✔
208
        ];
14✔
209

210
        return $GLOBALS['TSFE']->cObj->typoLink('', $config);
14✔
211
    }
212

213
    public function isAccessProtected(array $page): bool
214
    {
215
        return (0 !== (integer) $page['fe_group']);
56✔
216
    }
217

218
    public function isAccessGranted(array $page): bool
219
    {
220
        if (!$this->isAccessProtected($page)) {
49✔
221
            return true;
7✔
222
        }
223

224
        $groups = GeneralUtility::intExplode(',', (string) $page['fe_group']);
42✔
225

226
        $hide = (in_array(-1, $groups));
42✔
227
        $show = (in_array(-2, $groups));
42✔
228

229
        $userIsLoggedIn = (is_array($GLOBALS['TSFE']->fe_user->user));
42✔
230
        $userGroups = $GLOBALS['TSFE']->fe_user->groupData['uid'];
42✔
231
        $userIsInGrantedGroups = (0 < count(array_intersect($userGroups, $groups)));
42✔
232

233
        return (!$userIsLoggedIn && $hide) || ($userIsLoggedIn && $show) || ($userIsLoggedIn && $userIsInGrantedGroups);
42✔
234
    }
235

236
    public function isCurrent(int $pageUid): bool
237
    {
238
        return ($pageUid === (integer) $GLOBALS['TSFE']->id);
14✔
239
    }
240

241
    public function isActive(int $pageUid): bool
242
    {
243
        $rootLineData = $this->getRootLine();
28✔
244
        foreach ($rootLineData as $page) {
28✔
245
            if ((integer) $page['uid'] === $pageUid) {
28✔
246
                return true;
28✔
247
            }
248
        }
249

250
        return false;
7✔
251
    }
252

253
    public function shouldUseShortcutTarget(array $arguments): bool
254
    {
255
        $useShortcutTarget = (boolean) $arguments['useShortcutData'];
14✔
256
        if (array_key_exists('useShortcutTarget', $arguments)) {
14✔
257
            $useShortcutTarget = (boolean) $arguments['useShortcutTarget'];
14✔
258
        }
259

260
        return $useShortcutTarget;
14✔
261
    }
262

263
    public function shouldUseShortcutUid(array $arguments): bool
264
    {
265
        $useShortcutUid = (boolean) $arguments['useShortcutData'];
14✔
266
        if (array_key_exists('useShortcutUid', $arguments)) {
14✔
267
            $useShortcutUid = (boolean) $arguments['useShortcutUid'];
14✔
268
        }
269

270
        return $useShortcutUid;
14✔
271
    }
272

273
    /**
274
     * Determines the target page record for the provided page record
275
     * if it is configured as a shortcut in any of the possible modes.
276
     * Returns NULL otherwise.
277
     */
278
    public function getShortcutTargetPage(array $page): ?array
279
    {
280
        $dokType = (integer) ($page['doktype'] ?? PageRepository::DOKTYPE_DEFAULT);
63✔
281
        if ($dokType !== PageRepository::DOKTYPE_SHORTCUT) {
63✔
282
            return null;
21✔
283
        }
284
        $originalPageUid = $page['uid'];
42✔
285
        switch ($page['shortcut_mode']) {
42✔
286
            case PageRepository::SHORTCUT_MODE_PARENT_PAGE:
287
                $targetPage = $this->getPage($page['pid']);
7✔
288
                break;
7✔
289
            case PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE:
290
                $menu = $this->getMenu($page['shortcut'] > 0 ? $page['shortcut'] : $originalPageUid);
14✔
291
                $targetPage = (0 < count($menu)) ? $menu[array_rand($menu)] : $page;
14✔
292
                break;
14✔
293
            case PageRepository::SHORTCUT_MODE_FIRST_SUBPAGE:
294
                $menu = $this->getMenu($page['shortcut'] > 0 ? $page['shortcut'] : $originalPageUid);
14✔
295
                $targetPage = (0 < count($menu)) ? reset($menu) : $page;
14✔
296
                break;
14✔
297
            case PageRepository::SHORTCUT_MODE_NONE:
298
            default:
299
                $targetPage = $this->getPage($page['shortcut']);
7✔
300
        }
301
        return $targetPage;
42✔
302
    }
303

304
    /**
305
     * @return PageRepository
306
     * @codeCoverageIgnore
307
     */
308
    public function getPageRepository()
309
    {
310
        return clone ($GLOBALS['TSFE']->sys_page ?? $this->getPageRepositoryForBackendContext());
311
    }
312

313
    /**
314
     * @return PageRepository
315
     * @codeCoverageIgnore
316
     */
317
    protected function getPageRepositoryForBackendContext()
318
    {
319
        static $instance = null;
320
        if ($instance === null) {
321
            /** @var PageRepository $instance */
322
            $instance = GeneralUtility::makeInstance(PageRepository::class);
323
        }
324
        return $instance;
325
    }
326

327
    private function getRequest(): ServerRequestInterface
328
    {
329
        return $GLOBALS['TYPO3_REQUEST'];
×
330
    }
331
}
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