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

TYPO3-Headless / headless / 25552700505

08 May 2026 11:18AM UTC coverage: 74.7% (+1.7%) from 73.04%
25552700505

Pull #881

github

web-flow
Merge cf3b72b38 into c36c6b6bf
Pull Request #881: [WIP] TYPO3 v14 support - ongoing work - not stable

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

23 existing lines in 2 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
    {
52
        $allowedSites = $config['allowedSites'] ?? null;
×
53
        $sortingField = $config['sortingField'] ?? 'sorting';
×
54
        $customSorting = $config['sortingImplementation'] ?? null;
×
55

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

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

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

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

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

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
             */
82
            $sorting = GeneralUtility::makeInstance($customSorting, $sites, $pages, $sortingField);
×
83
            $sites = $sorting->sort();
×
84
        } else {
85
            usort($sites, static function (Site $siteA, Site $siteB) use ($pages, $sortingField) {
×
86
                // phpcs:ignore Generic.Files.LineLength
87
                return (int)$pages[$siteA->getRootPageId()][$sortingField] >= (int)$pages[$siteB->getRootPageId()][$sortingField] ? 1 : -1;
×
88
            });
×
89
        }
90

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

95
        return $this;
×
96
    }
97

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

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

114
    /**
115
     * @return Site
116
     */
117
    public function getCurrentRootPage(): Site
118
    {
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
    {
128
        $allSites = $this->siteFinder->getAllSites();
×
129

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

136
        $sites = [];
×
137

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

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
    {
155
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
156

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

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
    {
182
        $rootPagesId = array_values(array_map(static function (Site $item) {
×
183
            return $item->getRootPageId();
×
184
        }, $sites));
×
185

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

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

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

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

206
        $pages = [];
×
207

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

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