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

TYPO3-Headless / headless / 12065205662

28 Nov 2024 08:32AM UTC coverage: 72.782%. Remained the same
12065205662

push

github

web-flow
[BUGFIX] Don't render double slashes in file URLs (#796)

Without a frontendApiProxy in the site config, URLs to local files contain two slashes like www.mysite.org//fileadmin/my-image.jpg

1083 of 1488 relevant lines covered (72.78%)

8.4 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 ConnectionPool
35
     */
36
    private $connectionPool;
37
    /**
38
     * @var SiteFinder
39
     */
40
    private $siteFinder;
41
    /**
42
     * @var Site[]
43
     */
44
    private $sites;
45
    /**
46
     * @var array[]
47
     */
48
    private $pagesData;
49
    /**
50
     * @var Site
51
     */
52
    private $currentRootPage;
53

54
    public function __construct(ConnectionPool $connectionPool = null, SiteFinder $siteFinder = null)
55
    {
56
        $this->connectionPool = $connectionPool ?? GeneralUtility::makeInstance(ConnectionPool::class);
×
57
        $this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
×
58
    }
59

60
    /**
61
     * @param array<string, mixed> $config
62
     * @param int $siteUid
63
     */
64
    public function prepare(array $config, int $siteUid): self
65
    {
66
        $allowedSites = $config['allowedSites'] ?? null;
×
67
        $sortingField = $config['sortingField'] ?? 'sorting';
×
68
        $customSorting = $config['sortingImplementation'] ?? null;
×
69

70
        if ($sortingField === '') {
×
71
            $sortingField = 'sorting';
×
72
        }
73

74
        if ($allowedSites === null) {
×
75
            $allowedSites = [];
×
76
        } else {
77
            $allowedSites = GeneralUtility::intExplode(',', $allowedSites, true);
×
78
        }
79

80
        $sitesFromPid = (int)($config['sitesFromPid'] ?? 0);
×
81

82
        if ($sitesFromPid) {
×
83
            $allowedSites = $this->fetchAvailableRootSitesByPid($sitesFromPid);
×
84
        }
85

86
        $sites = $this->filterSites($allowedSites);
×
87
        $pages = $this->fetchPageData($sites, $config);
×
88

89
        if ($customSorting !== null) {
×
90
            if (!is_a($customSorting, SiteSortingInterface::class, true)) {
×
91
                throw new InvalidArgumentException('Invalid implementation of SiteSortingInterface');
×
92
            }
93
            /**
94
             * @var SiteSortingInterface $sorting
95
             */
96
            $sorting = GeneralUtility::makeInstance($customSorting, $sites, $pages, $sortingField);
×
97
            $sites = $sorting->sort();
×
98
        } else {
99
            usort($sites, static function (Site $siteA, Site $siteB) use ($pages, $sortingField) {
×
100
                // phpcs:ignore Generic.Files.LineLength
101
                return (int)$pages[$siteA->getRootPageId()][$sortingField] >= (int)$pages[$siteB->getRootPageId()][$sortingField] ? 1 : -1;
×
102
            });
×
103
        }
104

105
        $this->sites = $sites;
×
106
        $this->pagesData = $pages;
×
107
        $this->currentRootPage = $this->siteFinder->getSiteByPageId($siteUid);
×
108

109
        return $this;
×
110
    }
111

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

120
    /**
121
     * @return array<int, array>
122
     */
123
    public function getPages(): array
124
    {
125
        return $this->pagesData;
×
126
    }
127

128
    /**
129
     * @return Site
130
     */
131
    public function getCurrentRootPage(): Site
132
    {
133
        return $this->currentRootPage;
×
134
    }
135

136
    /**
137
     * @param array<int> $allowedSites
138
     * @return array<Site>
139
     */
140
    private function filterSites(array $allowedSites = []): array
141
    {
142
        $allSites = $this->siteFinder->getAllSites();
×
143

144
        if (count($allowedSites) === 0) {
×
145
            return array_filter($allSites, static function (Site $site) {
×
146
                return $site->getConfiguration()['headless'] ?? false;
×
147
            });
×
148
        }
149

150
        $sites = [];
×
151

152
        foreach ($allSites as $site) {
×
153
            if (in_array($site->getRootPageId(), $allowedSites, true) &&
×
154
            $site->getConfiguration()['headless'] ?? false) {
×
155
                $sites[] = $site;
×
156
            }
157
        }
158

159
        return $sites;
×
160
    }
161

162
    /**
163
     * @param int $pid
164
     * @return array<int>
165
     * @throws Exception
166
     */
167
    private function fetchAvailableRootSitesByPid(int $pid): array
168
    {
169
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
170

171
        $pagesData = $queryBuilder
×
172
            ->select('uid')
×
173
            ->from('pages')
×
174
            ->andWhere(
×
175
                $queryBuilder->expr()->eq('is_siteroot', $queryBuilder->createNamedParameter(1, Connection::PARAM_INT)),
×
176
                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)),
×
177
            )
×
178
            ->executeQuery()
×
179
            ->fetchAllAssociative();
×
180

181
        return array_map(static function (array $item): int {
×
182
            return (int)$item['uid'];
×
183
        }, $pagesData);
×
184
    }
185

186
    /**
187
     * Fetches pages' titles & translations (if site has more than one language) of root pages
188
     *
189
     * @param array<Site> $sites
190
     * @param array<string, mixed> $config
191
     * @return array<int, array>
192
     * @throws Exception
193
     */
194
    private function fetchPageData(array $sites, array $config = []): array
195
    {
196
        $rootPagesId = array_values(array_map(static function (Site $item) {
×
197
            return $item->getRootPageId();
×
198
        }, $sites));
×
199

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

202
        if (count($columns) === 0) {
×
203
            $columns = ['uid', 'title', 'sorting'];
×
204
        }
205

206
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
×
207

208
        $pagesData = $queryBuilder
×
209
            ->select(...$columns)
×
210
            ->from('pages')
×
211
            ->where(
×
212
                $queryBuilder->expr()->in(
×
213
                    'uid',
×
214
                    $queryBuilder->createNamedParameter($rootPagesId, Connection::PARAM_INT_ARRAY)
×
215
                )
×
216
            )
×
217
            ->executeQuery()
×
218
            ->fetchAllAssociative();
×
219

220
        $pages = [];
×
221

222
        foreach ($pagesData as $page) {
×
223
            $pages[(int)$page['uid']] = $page;
×
224
        }
225

226
        return $pages;
×
227
    }
228
}
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