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

TYPO3-Headless / headless / 25562644868

08 May 2026 02:57PM UTC coverage: 74.7%. Remained the same
25562644868

push

github

lukaszuznanski
[FEATURE] TYPO3 v14 compatibility (5.0.0-rc1)

Drops support for TYPO3 12/13. Bumps required PHP/TYPO3 versions,
removes deprecated PreviewController XClass, adds LanguageMenuProcessor
and assorted v14 adaptations across data processors, middlewares,
SEO, and tests.

38 of 58 new or added lines in 10 files covered. (65.52%)

63 existing lines in 6 files now uncovered.

1181 of 1581 relevant lines covered (74.7%)

6.63 hits per line

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

0.0
/Classes/DataProcessing/RootSiteProcessing/SiteProvider.php
1
<?php
2

3
/*
4
 * This file is part of the "headless" Extension for TYPO3 CMS.
5
 *
6
 * For the full copyright and license information, please read the
7
 * LICENSE.md file that was distributed with this source code.
8
 */
9

10
declare(strict_types=1);
11

12
namespace FriendsOfTYPO3\Headless\DataProcessing\RootSiteProcessing;
13

14
use Doctrine\DBAL\ArrayParameterType;
15
use Doctrine\DBAL\Driver\Exception;
16
use InvalidArgumentException;
17
use TYPO3\CMS\Core\Database\Connection;
18
use TYPO3\CMS\Core\Database\ConnectionPool;
19
use TYPO3\CMS\Core\Site\Entity\Site;
20
use TYPO3\CMS\Core\Site\SiteFinder;
21

22
use TYPO3\CMS\Core\Utility\GeneralUtility;
23

24
use function array_filter;
25
use function array_map;
26
use function array_values;
27
use function count;
28
use function in_array;
29
use function is_a;
30
use function usort;
31

32
class SiteProvider implements SiteProviderInterface
33
{
34
    /**
35
     * @var Site[]
36
     */
37
    private array $sites;
38
    /**
39
     * @var array[]
40
     */
41
    private array $pagesData;
42
    private Site $currentRootPage;
43

44
    public function __construct(protected ConnectionPool $connectionPool, protected SiteFinder $siteFinder) {}
45

46
    /**
47
     * @param array<string, mixed> $config
48
     * @param int $siteUid
49
     */
50
    public function prepare(array $config, int $siteUid): self
51
    {
UNCOV
52
        $allowedSites = $config['allowedSites'] ?? null;
×
53
        $sortingField = $config['sortingField'] ?? 'sorting';
×
54
        $customSorting = $config['sortingImplementation'] ?? null;
×
55

UNCOV
56
        if ($sortingField === '') {
×
57
            $sortingField = 'sorting';
×
58
        }
59

UNCOV
60
        if ($allowedSites === null) {
×
61
            $allowedSites = [];
×
62
        } else {
UNCOV
63
            $allowedSites = GeneralUtility::intExplode(',', $allowedSites, true);
×
64
        }
65

UNCOV
66
        $sitesFromPid = (int)($config['sitesFromPid'] ?? 0);
×
67

UNCOV
68
        if ($sitesFromPid) {
×
69
            $allowedSites = $this->fetchAvailableRootSitesByPid($sitesFromPid);
×
70
        }
71

UNCOV
72
        $sites = $this->filterSites($allowedSites);
×
73
        $pages = $this->fetchPageData($sites, $config);
×
74

UNCOV
75
        if ($customSorting !== null) {
×
76
            if (!is_a($customSorting, SiteSortingInterface::class, true)) {
×
77
                throw new InvalidArgumentException('Invalid implementation of SiteSortingInterface');
×
78
            }
79
            /**
80
             * @var SiteSortingInterface $sorting
81
             */
UNCOV
82
            $sorting = GeneralUtility::makeInstance($customSorting, $sites, $pages, $sortingField);
×
83
            $sites = $sorting->sort();
×
84
        } else {
UNCOV
85
            usort($sites, static function (Site $siteA, Site $siteB) use ($pages, $sortingField) {
×
86
                // phpcs:ignore Generic.Files.LineLength
UNCOV
87
                return (int)$pages[$siteA->getRootPageId()][$sortingField] >= (int)$pages[$siteB->getRootPageId()][$sortingField] ? 1 : -1;
×
88
            });
×
89
        }
90

UNCOV
91
        $this->sites = $sites;
×
92
        $this->pagesData = $pages;
×
93
        $this->currentRootPage = $this->siteFinder->getSiteByPageId($siteUid);
×
94

UNCOV
95
        return $this;
×
96
    }
97

98
    /**
99
     * @return array<Site>
100
     */
101
    public function getSites(): array
102
    {
UNCOV
103
        return $this->sites;
×
104
    }
105

106
    /**
107
     * @return array<int, array>
108
     */
109
    public function getPages(): array
110
    {
UNCOV
111
        return $this->pagesData;
×
112
    }
113

114
    /**
115
     * @return Site
116
     */
117
    public function getCurrentRootPage(): Site
118
    {
UNCOV
119
        return $this->currentRootPage;
×
120
    }
121

122
    /**
123
     * @param array<int> $allowedSites
124
     * @return array<Site>
125
     */
126
    private function filterSites(array $allowedSites = []): array
127
    {
UNCOV
128
        $allSites = $this->siteFinder->getAllSites();
×
129

UNCOV
130
        if (count($allowedSites) === 0) {
×
131
            return array_filter($allSites, static function (Site $site) {
×
132
                return $site->getConfiguration()['headless'] ?? false;
×
133
            });
×
134
        }
135

UNCOV
136
        $sites = [];
×
137

UNCOV
138
        foreach ($allSites as $site) {
×
139
            if (in_array($site->getRootPageId(), $allowedSites, true) &&
×
140
            $site->getConfiguration()['headless'] ?? false) {
×
141
                $sites[] = $site;
×
142
            }
143
        }
144

UNCOV
145
        return $sites;
×
146
    }
147

148
    /**
149
     * @param int $pid
150
     * @return array<int>
151
     * @throws Exception
152
     */
153
    private function fetchAvailableRootSitesByPid(int $pid): array
154
    {
UNCOV
155
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
156

UNCOV
157
        $pagesData = $queryBuilder
×
158
            ->select('uid')
×
159
            ->from('pages')
×
160
            ->andWhere(
×
161
                $queryBuilder->expr()->eq('is_siteroot', $queryBuilder->createNamedParameter(1, Connection::PARAM_INT)),
×
162
                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)),
×
163
            )
×
164
            ->executeQuery()
×
165
            ->fetchAllAssociative();
×
166

UNCOV
167
        return array_map(static function (array $item): int {
×
168
            return (int)$item['uid'];
×
169
        }, $pagesData);
×
170
    }
171

172
    /**
173
     * Fetches pages' titles & translations (if site has more than one language) of root pages
174
     *
175
     * @param array<Site> $sites
176
     * @param array<string, mixed> $config
177
     * @return array<int, array>
178
     * @throws Exception
179
     */
180
    private function fetchPageData(array $sites, array $config = []): array
181
    {
UNCOV
182
        $rootPagesId = array_values(array_map(static function (Site $item) {
×
183
            return $item->getRootPageId();
×
184
        }, $sites));
×
185

UNCOV
186
        $columns = GeneralUtility::trimExplode(',', $config['dbColumns'] ?? 'uid,title,sorting', true);
×
187

UNCOV
188
        if (count($columns) === 0) {
×
189
            $columns = ['uid', 'title', 'sorting'];
×
190
        }
191

UNCOV
192
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
193

UNCOV
194
        $pagesData = $queryBuilder
×
195
            ->select(...$columns)
×
196
            ->from('pages')
×
197
            ->where(
×
198
                $queryBuilder->expr()->in(
×
199
                    'uid',
×
NEW
200
                    $queryBuilder->createNamedParameter($rootPagesId, ArrayParameterType::INTEGER)
×
201
                )
×
202
            )
×
203
            ->executeQuery()
×
204
            ->fetchAllAssociative();
×
205

UNCOV
206
        $pages = [];
×
207

UNCOV
208
        foreach ($pagesData as $page) {
×
209
            $pages[(int)$page['uid']] = $page;
×
210
        }
211

UNCOV
212
        return $pages;
×
213
    }
214
}
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