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

TYPO3-Headless / headless / 15632475898

13 Jun 2025 10:31AM UTC coverage: 73.085% (-0.05%) from 73.13%
15632475898

push

github

web-flow
[TASK] Set version v2.1 (#816)

[FEATURE] Allow to replace implementation of HeadlessMode

Also introduce option to site settings `headless.preview.overrideMode` to define behaviour in case of mixed mode when click preview button

# Conflicts:
#	Classes/Middleware/ElementBodyResponseMiddleware.php

9 of 9 new or added lines in 7 files covered. (100.0%)

66 existing lines in 3 files now uncovered.

1097 of 1501 relevant lines covered (73.08%)

8.22 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
    {
UNCOV
51
        $allowedSites = $config['allowedSites'] ?? null;
×
UNCOV
52
        $sortingField = $config['sortingField'] ?? 'sorting';
×
UNCOV
53
        $customSorting = $config['sortingImplementation'] ?? null;
×
54

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

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

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

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

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

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

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

UNCOV
94
        return $this;
×
95
    }
96

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

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

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

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

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

UNCOV
135
        $sites = [];
×
136

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

144
        return $sites;
×
145
    }
146

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

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

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

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

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

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

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

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

UNCOV
205
        $pages = [];
×
206

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

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

© 2025 Coveralls, Inc