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

TYPO3-Headless / headless / 26439917307

26 May 2026 07:58AM UTC coverage: 70.455% (-5.0%) from 75.459%
26439917307

Pull #893

github

web-flow
Merge 00997f766 into 79b7c5472
Pull Request #893: [TASK] Reintroduce missing features, extension cleanup

360 of 523 new or added lines in 32 files covered. (68.83%)

167 existing lines in 7 files now uncovered.

1364 of 1936 relevant lines covered (70.45%)

7.36 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<int, array<string, mixed>>
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 {
NEW
85
            usort($sites, static fn(Site $siteA, Site $siteB): int =>
×
NEW
86
                (int)$pages[$siteA->getRootPageId()][$sortingField] <=> (int)$pages[$siteB->getRootPageId()][$sortingField]);
×
87
        }
88

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

93
        return $this;
×
94
    }
95

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

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

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

120
    /**
121
     * @param array<int> $allowedSites
122
     * @return array<Site>
123
     */
124
    private function filterSites(array $allowedSites = []): array
125
    {
NEW
126
        return array_filter(
×
NEW
127
            $this->siteFinder->getAllSites(),
×
NEW
128
            static fn(Site $site): bool =>
×
NEW
129
                ($site->getConfiguration()['headless'] ?? false)
×
NEW
130
                && ($allowedSites === [] || in_array($site->getRootPageId(), $allowedSites, true))
×
NEW
131
        );
×
132
    }
133

134
    /**
135
     * @param int $pid
136
     * @return array<int>
137
     * @throws Exception
138
     */
139
    private function fetchAvailableRootSitesByPid(int $pid): array
140
    {
141
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
142

143
        $pagesData = $queryBuilder
×
144
            ->select('uid')
×
145
            ->from('pages')
×
146
            ->andWhere(
×
147
                $queryBuilder->expr()->eq('is_siteroot', $queryBuilder->createNamedParameter(1, Connection::PARAM_INT)),
×
148
                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)),
×
149
            )
×
150
            ->executeQuery()
×
151
            ->fetchAllAssociative();
×
152

153
        return array_map(static function (array $item): int {
×
154
            return (int)$item['uid'];
×
155
        }, $pagesData);
×
156
    }
157

158
    /**
159
     * Fetches pages' titles & translations (if site has more than one language) of root pages
160
     *
161
     * @param array<Site> $sites
162
     * @param array<string, mixed> $config
163
     * @return array<int, array<string, mixed>>
164
     * @throws Exception
165
     */
166
    private function fetchPageData(array $sites, array $config = []): array
167
    {
168
        $rootPagesId = array_values(array_map(static function (Site $item) {
×
169
            return $item->getRootPageId();
×
170
        }, $sites));
×
171

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

174
        if (count($columns) === 0) {
×
175
            $columns = ['uid', 'title', 'sorting'];
×
176
        }
177

178
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
179

180
        $pagesData = $queryBuilder
×
181
            ->select(...$columns)
×
182
            ->from('pages')
×
183
            ->where(
×
184
                $queryBuilder->expr()->in(
×
185
                    'uid',
×
186
                    $queryBuilder->createNamedParameter($rootPagesId, ArrayParameterType::INTEGER)
×
187
                )
×
188
            )
×
189
            ->executeQuery()
×
190
            ->fetchAllAssociative();
×
191

192
        $pages = [];
×
193

194
        foreach ($pagesData as $page) {
×
195
            $pages[(int)$page['uid']] = $page;
×
196
        }
197

198
        return $pages;
×
199
    }
200
}
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