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

TYPO3-Headless / headless / 26440568390

26 May 2026 08:13AM UTC coverage: 72.91%. First build
26440568390

Pull #892

github

web-flow
Merge da0000d78 into fdf45e8bf
Pull Request #892: [BUGFIX] Do not modify external links, fixes for cropping, small optimization backported from 5.x branch

48 of 60 new or added lines in 11 files covered. (80.0%)

1160 of 1591 relevant lines covered (72.91%)

8.44 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\Driver\Exception;
15
use InvalidArgumentException;
16
use TYPO3\CMS\Core\Database\Connection;
17
use TYPO3\CMS\Core\Database\ConnectionPool;
18
use TYPO3\CMS\Core\Site\Entity\Site;
19
use TYPO3\CMS\Core\Site\SiteFinder;
20

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

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

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

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

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

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

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

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

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

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

74
        if ($customSorting !== null) {
×
75
            if (!is_a($customSorting, SiteSortingInterface::class, true)) {
×
76
                throw new InvalidArgumentException('Invalid implementation of SiteSortingInterface');
×
77
            }
78
            /**
79
             * @var SiteSortingInterface $sorting
80
             */
81
            $sorting = GeneralUtility::makeInstance($customSorting, $sites, $pages, $sortingField);
×
82
            $sites = $sorting->sort();
×
83
        } else {
NEW
84
            usort($sites, static fn(Site $siteA, Site $siteB): int =>
×
NEW
85
                (int)$pages[$siteA->getRootPageId()][$sortingField] <=> (int)$pages[$siteB->getRootPageId()][$sortingField]);
×
86
        }
87

88
        $this->sites = $sites;
×
89
        $this->pagesData = $pages;
×
90
        $this->currentRootPage = $this->siteFinder->getSiteByPageId($siteUid);
×
91

92
        return $this;
×
93
    }
94

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

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

111
    /**
112
     * @return Site
113
     */
114
    public function getCurrentRootPage(): Site
115
    {
116
        return $this->currentRootPage;
×
117
    }
118

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

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

133
        $sites = [];
×
134

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

142
        return $sites;
×
143
    }
144

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

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

164
        return array_map(static function (array $item): int {
×
165
            return (int)$item['uid'];
×
166
        }, $pagesData);
×
167
    }
168

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

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

185
        if (count($columns) === 0) {
×
186
            $columns = ['uid', 'title', 'sorting'];
×
187
        }
188

189
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
190

191
        $pagesData = $queryBuilder
×
192
            ->select(...$columns)
×
193
            ->from('pages')
×
194
            ->where(
×
195
                $queryBuilder->expr()->in(
×
196
                    'uid',
×
197
                    $queryBuilder->createNamedParameter($rootPagesId, Connection::PARAM_INT_ARRAY)
×
198
                )
×
199
            )
×
200
            ->executeQuery()
×
201
            ->fetchAllAssociative();
×
202

203
        $pages = [];
×
204

205
        foreach ($pagesData as $page) {
×
206
            $pages[(int)$page['uid']] = $page;
×
207
        }
208

209
        return $pages;
×
210
    }
211
}
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