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

FluidTYPO3 / vhs / 13566190336

27 Feb 2025 12:18PM UTC coverage: 72.127% (-0.6%) from 72.746%
13566190336

push

github

NamelessCoder
[TER] 7.1.0

5649 of 7832 relevant lines covered (72.13%)

20.01 hits per line

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

89.76
/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 TYPO3\CMS\Core\Context\Context;
13
use TYPO3\CMS\Core\Context\LanguageAspect;
14
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
15
use TYPO3\CMS\Core\SingletonInterface;
16
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
17
use TYPO3\CMS\Core\Utility\GeneralUtility;
18
use TYPO3\CMS\Core\Utility\RootlineUtility;
19
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
20

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

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

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

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

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

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

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

75
    public function getRootLine(
76
        ?int $pageUid = null,
77
        bool $reverse = false
78
    ): array {
79
        if (null === $pageUid) {
28✔
80
            $pageUid = $GLOBALS['TSFE']->id;
14✔
81
        }
82
        /** @var RootlineUtility $rootLineUtility */
83
        $rootLineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageUid);
28✔
84
        $rootline = $rootLineUtility->get();
28✔
85
        if ($reverse) {
28✔
86
            $rootline = array_reverse($rootline);
14✔
87
        }
88
        return $rootline;
28✔
89
    }
90

91
    protected function getPageConstraints(
92
        array $excludePages = [],
93
        bool $includeNotInMenu = false,
94
        bool $includeMenuSeparator = false
95
    ): string {
96
        $constraints = [];
7✔
97

98
        $types = [
7✔
99
            PageRepository::DOKTYPE_BE_USER_SECTION,
7✔
100
            PageRepository::DOKTYPE_SYSFOLDER
7✔
101
        ];
7✔
102

103
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '12.4', '<=')) {
7✔
104
            $types[] = PageRepository::DOKTYPE_RECYCLER;
3✔
105
        }
106

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

109
        if ($includeNotInMenu === false) {
7✔
110
            $constraints[] = 'nav_hide = 0';
7✔
111
        }
112

113
        if ($includeMenuSeparator === false) {
7✔
114
            $constraints[] = 'doktype != ' . PageRepository::DOKTYPE_SPACER;
7✔
115
        }
116

117
        if (0 < count($excludePages)) {
7✔
118
            $constraints[] = 'uid NOT IN (' . implode(',', $excludePages) . ')';
×
119
        }
120

121
        return 'AND ' . implode(' AND ', $constraints);
7✔
122
    }
123

124
    /**
125
     * @param array|integer|null $page
126
     */
127
    public function hidePageForLanguageUid($page = null, int $languageUid = -1, bool $normalWhenNoLanguage = true): bool
128
    {
129
        if (is_array($page)) {
7✔
130
            $pageUid = $page['uid'];
7✔
131
            $pageRecord = $page;
7✔
132
        } else {
133
            $pageUid = (0 === (integer) $page) ? $GLOBALS['TSFE']->id : (integer) $page;
×
134
            $pageRecord = $this->getPage($pageUid);
×
135
        }
136
        if (-1 === $languageUid) {
7✔
137
            if (class_exists(LanguageAspect::class)) {
7✔
138
                /** @var Context $context */
139
                $context = GeneralUtility::makeInstance(Context::class);
7✔
140
                /** @var LanguageAspect $languageAspect */
141
                $languageAspect = $context->getAspect('language');
7✔
142
                $languageUid = $languageAspect->getId();
7✔
143
            } else {
144
                $languageUid = $GLOBALS['TSFE']->sys_language_uid;
×
145
            }
146
        }
147

148
        $l18nCfg = $pageRecord['l18n_cfg'] ?? 0;
7✔
149
        if (class_exists(PageTranslationVisibility::class)) {
7✔
150
            /** @var PageTranslationVisibility $visibilityBitSet */
151
            $visibilityBitSet = GeneralUtility::makeInstance(
6✔
152
                PageTranslationVisibility::class,
6✔
153
                $l18nCfg
6✔
154
            );
6✔
155
            $hideIfNotTranslated = $visibilityBitSet->shouldHideTranslationIfNoTranslatedRecordExists();
6✔
156
            $hideIfDefaultLanguage = $visibilityBitSet->shouldBeHiddenInDefaultLanguage();
6✔
157
        } else {
158
            $hideIfNotTranslated = (boolean) GeneralUtility::hideIfNotTranslated($l18nCfg);
1✔
159
            $hideIfDefaultLanguage = (boolean) GeneralUtility::hideIfDefaultLanguage($l18nCfg);
1✔
160
        }
161

162
        $pageOverlay = [];
7✔
163
        if (0 !== $languageUid) {
7✔
164
            $pageOverlay = $this->getPageRepository()->getPageOverlay($pageUid, $languageUid);
×
165
        }
166
        $translationAvailable = (0 !== count($pageOverlay));
7✔
167

168
        return
7✔
169
            ($hideIfNotTranslated && (0 !== $languageUid) && !$translationAvailable) ||
7✔
170
            ($hideIfDefaultLanguage && ((0 === $languageUid) || !$translationAvailable)) ||
7✔
171
            (!$normalWhenNoLanguage && (0 !== $languageUid) && !$translationAvailable);
7✔
172
    }
173

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

200
        return $GLOBALS['TSFE']->cObj->typoLink('', $config);
14✔
201
    }
202

203
    public function isAccessProtected(array $page): bool
204
    {
205
        return (0 !== (integer) $page['fe_group']);
56✔
206
    }
207

208
    public function isAccessGranted(array $page): bool
209
    {
210
        if (!$this->isAccessProtected($page)) {
49✔
211
            return true;
7✔
212
        }
213

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

216
        $hide = (in_array(-1, $groups));
42✔
217
        $show = (in_array(-2, $groups));
42✔
218

219
        $userIsLoggedIn = (is_array($GLOBALS['TSFE']->fe_user->user));
42✔
220
        $userGroups = $GLOBALS['TSFE']->fe_user->groupData['uid'];
42✔
221
        $userIsInGrantedGroups = (0 < count(array_intersect($userGroups, $groups)));
42✔
222

223
        return (!$userIsLoggedIn && $hide) || ($userIsLoggedIn && $show) || ($userIsLoggedIn && $userIsInGrantedGroups);
42✔
224
    }
225

226
    public function isCurrent(int $pageUid): bool
227
    {
228
        return ($pageUid === (integer) $GLOBALS['TSFE']->id);
14✔
229
    }
230

231
    public function isActive(int $pageUid): bool
232
    {
233
        $rootLineData = $this->getRootLine();
28✔
234
        foreach ($rootLineData as $page) {
28✔
235
            if ((integer) $page['uid'] === $pageUid) {
28✔
236
                return true;
28✔
237
            }
238
        }
239

240
        return false;
7✔
241
    }
242

243
    public function shouldUseShortcutTarget(array $arguments): bool
244
    {
245
        $useShortcutTarget = (boolean) $arguments['useShortcutData'];
14✔
246
        if (array_key_exists('useShortcutTarget', $arguments)) {
14✔
247
            $useShortcutTarget = (boolean) $arguments['useShortcutTarget'];
14✔
248
        }
249

250
        return $useShortcutTarget;
14✔
251
    }
252

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

260
        return $useShortcutUid;
14✔
261
    }
262

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

294
    /**
295
     * @return PageRepository
296
     * @codeCoverageIgnore
297
     */
298
    public function getPageRepository()
299
    {
300
        return clone ($GLOBALS['TSFE']->sys_page ?? $this->getPageRepositoryForBackendContext());
301
    }
302

303
    /**
304
     * @return PageRepository
305
     * @codeCoverageIgnore
306
     */
307
    protected function getPageRepositoryForBackendContext()
308
    {
309
        static $instance = null;
310
        if ($instance === null) {
311
            /** @var PageRepository $instance */
312
            $instance = GeneralUtility::makeInstance(PageRepository::class);
313
        }
314
        return $instance;
315
    }
316
}
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