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

TYPO3-Headless / headless / 5613033366

pending completion
5613033366

push

github

web-flow
Bump version to 4.1.1 (#632)

777 of 1251 relevant lines covered (62.11%)

2.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/Classes/Utility/UrlUtility.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\Utility;
13

14
use Psr\Http\Message\ServerRequestInterface;
15
use Psr\Log\LoggerAwareInterface;
16
use Psr\Log\LoggerAwareTrait;
17
use Symfony\Component\ExpressionLanguage\SyntaxError;
18
use TYPO3\CMS\Core\Configuration\Features;
19
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
20
use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
21
use TYPO3\CMS\Core\Http\Uri;
22
use TYPO3\CMS\Core\Site\Entity\Site;
23
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
24
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
25
use TYPO3\CMS\Core\Site\SiteFinder;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27

28
use function array_merge;
29
use function rtrim;
30
use function str_contains;
31

32
class UrlUtility implements LoggerAwareInterface, HeadlessFrontendUrlInterface
33
{
34
    use LoggerAwareTrait;
35

36
    private Features $features;
37
    private Resolver $resolver;
38
    private SiteFinder $siteFinder;
39
    private array $conf = [];
40
    private array $variants = [];
41

42
    public function __construct(
43
        ?Features $features = null,
44
        ?Resolver $resolver = null,
45
        ?SiteFinder $siteFinder = null,
46
        ?ServerRequestInterface $serverRequest = null
47
    ) {
48
        $this->features = $features ?? GeneralUtility::makeInstance(Features::class);
17✔
49
        $this->resolver = $resolver ?? GeneralUtility::makeInstance(Resolver::class, 'site', []);
17✔
50
        $this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
17✔
51
        $request = $serverRequest ?? ($GLOBALS['TYPO3_REQUEST'] ?? null);
17✔
52

53
        if ($request instanceof ServerRequestInterface) {
17✔
54
            $this->extractConfigurationFromRequest($request, $this);
2✔
55
        }
56
    }
57

58
    public function withSite(Site $site): HeadlessFrontendUrlInterface
59
    {
60
        return $this->handleSiteConfiguration($site, clone $this);
11✔
61
    }
62

63
    public function withRequest(ServerRequestInterface $request): HeadlessFrontendUrlInterface
64
    {
65
        return $this->extractConfigurationFromRequest($request, clone $this);
1✔
66
    }
67

68
    public function withLanguage(SiteLanguage $language): HeadlessFrontendUrlInterface
69
    {
70
        return $this->handleLanguageConfiguration($language, clone $this);
1✔
71
    }
72

73
    public function getFrontendUrlWithSite($url, SiteInterface $site, string $returnField = 'frontendBase'): string
74
    {
75
        $configuration = $site->getConfiguration();
9✔
76

77
        if (!($configuration['headless'] ?? false)) {
9✔
78
            return $url;
8✔
79
        }
80

81
        try {
82
            $base = $site->getBase()->getHost();
4✔
83
            $port = $site->getBase()->getPort();
4✔
84
            $frontendBaseUrl = $this->resolveWithVariants(
4✔
85
                $configuration[$returnField] ?? '',
4✔
86
                $configuration['baseVariants'] ?? [],
4✔
87
                $returnField
4✔
88
            );
4✔
89

90
            if ($frontendBaseUrl === '') {
4✔
91
                return $url;
×
92
            }
93

94
            $frontendBase = GeneralUtility::makeInstance(Uri::class, $this->sanitizeBaseUrl($frontendBaseUrl));
4✔
95
            $frontBase = $frontendBase->getHost();
4✔
96
            $frontPort = $frontendBase->getPort();
4✔
97
            $targetUri = new Uri($this->sanitizeBaseUrl($url));
4✔
98

99
            if (str_contains($url, $base)) {
4✔
100
                $targetUri = $targetUri->withHost($frontBase);
4✔
101
            }
102

103
            if ($port === $frontPort) {
4✔
104
                return (string)$targetUri;
2✔
105
            }
106

107
            if ($frontPort) {
2✔
108
                $targetUri = $targetUri->withPort($frontPort);
2✔
109
            }
110

111
            return (string)$targetUri;
2✔
112
        } catch (SiteNotFoundException $e) {
×
113
            $this->logError($e->getMessage());
×
114
        }
115

116
        return $url;
×
117
    }
118

119
    public function getFrontendUrlForPage(string $url, int $pageUid, string $returnField = 'frontendBase'): string
120
    {
121
        try {
122
            return $this->getFrontendUrlWithSite(
8✔
123
                $url,
8✔
124
                $this->siteFinder->getSiteByPageId($pageUid),
8✔
125
                $returnField
8✔
126
            );
8✔
127
        } catch (SiteNotFoundException $e) {
1✔
128
            $this->logError($e->getMessage());
1✔
129
        }
130

131
        return $url;
1✔
132
    }
133

134
    public function getFrontendUrl(): string
135
    {
136
        return $this->resolveWithVariants($this->conf['frontendBase'] ?? '', $this->variants);
5✔
137
    }
138

139
    public function getProxyUrl(): string
140
    {
141
        return $this->resolveWithVariants($this->conf['frontendApiProxy'] ?? '', $this->variants, 'frontendApiProxy');
5✔
142
    }
143

144
    public function getStorageProxyUrl(): string
145
    {
146
        return $this->resolveWithVariants($this->conf['frontendFileApi'] ?? '', $this->variants, 'frontendFileApi');
4✔
147
    }
148

149
    public function resolveKey(string $key): string
150
    {
151
        return $this->resolveWithVariants($this->conf[$key] ?? '', $this->variants, $key);
4✔
152
    }
153

154
    public function prepareRelativeUrlIfPossible(string $targetUrl): string
155
    {
156
        $parsedTargetUrl = new Uri($this->sanitizeBaseUrl($targetUrl));
2✔
157
        $parsedProjectFrontendUrl = new Uri($this->sanitizeBaseUrl($this->getFrontendUrl()));
2✔
158

159
        if ($parsedTargetUrl->getHost() === $parsedProjectFrontendUrl->getHost()) {
2✔
160
            return $parsedTargetUrl->getPath() . ($parsedTargetUrl->getQuery() ? '?' . $parsedTargetUrl->getQuery() : '');
2✔
161
        }
162

163
        return $targetUrl;
2✔
164
    }
165

166
    /**
167
     * @codeCoverageIgnore
168
     */
169
    protected function logError(string $message): void
170
    {
171
        if ($this->logger) {
172
            $this->logger->error($message);
173
        }
174
    }
175

176
    /**
177
     * If a site base contains "/" or "www.domain.com", it is ensured that
178
     * parse_url() can handle this kind of configuration properly.
179
     */
180
    private function sanitizeBaseUrl(string $base): string
181
    {
182
        // no protocol ("//") and the first part is no "/" (path), means that this is a domain like
183
        // "www.domain.com/blabla", and we want to ensure that this one then gets a "no-scheme agnostic" part
184
        if (!empty($base) && !str_contains($base, '//')   && $base[0] !== '/') {
6✔
185
            // either a scheme is added, or no scheme but with domain, or a path which is not absolute
186
            // make the base prefixed with a slash, so it is recognized as path, not as domain
187
            // treat as path
188
            if (!str_contains($base, '.')) {
1✔
189
                $base = '/' . $base;
1✔
190
            } else {
191
                // treat as domain name
192
                $base = '//' . $base;
1✔
193
            }
194
        }
195
        return $base;
6✔
196
    }
197

198
    private function resolveWithVariants(
199
        string $frontendUrl,
200
        array $variants = [],
201
        string $returnField = 'frontendBase'
202
    ): string {
203
        $frontendUrl = rtrim($frontendUrl, '/');
12✔
204
        if ($variants === []) {
12✔
205
            return $frontendUrl;
1✔
206
        }
207

208
        foreach ($variants as $baseVariant) {
12✔
209
            try {
210
                if ($this->resolver->evaluate($baseVariant['condition'])) {
12✔
211
                    return rtrim($baseVariant[$returnField] ?? '', '/');
12✔
212
                }
213
            } catch (SyntaxError $e) {
1✔
214
                $this->logError($e->getMessage());
1✔
215
                // silently fail and do not evaluate
216
                // no logger here, as Site is currently cached and serialized
217
            }
218
        }
219
        return $frontendUrl;
3✔
220
    }
221

222
    private function handleLanguageConfiguration(SiteLanguage $language, HeadlessFrontendUrlInterface $object): HeadlessFrontendUrlInterface
223
    {
224
        $langConf = $language->toArray();
2✔
225
        $variants = $langConf['baseVariants'] ?? [];
2✔
226
        $frontendBase = trim($langConf['frontendBase'] ?? '');
2✔
227
        $frontendApiProxy = trim($langConf['frontendApiProxy'] ?? '');
2✔
228
        $frontendFileApi = trim($langConf['frontendFileApi'] ?? '');
2✔
229
        $overrides = [];
2✔
230

231
        if ($frontendBase !== '') {
2✔
232
            $overrides['frontendBase'] =  $frontendBase;
1✔
233
        }
234

235
        if ($frontendApiProxy !== '') {
2✔
236
            $overrides['frontendApiProxy'] =  $frontendApiProxy;
1✔
237
        }
238

239
        if ($frontendFileApi !== '') {
2✔
240
            $overrides['frontendFileApi'] =  $frontendFileApi;
1✔
241
        }
242

243
        $object->conf = array_merge($object->conf, $overrides);
2✔
244

245
        if ($variants !== []) {
2✔
246
            $object->variants = $variants;
2✔
247
        }
248

249
        return $object;
2✔
250
    }
251

252
    private function handleSiteConfiguration(Site $site, UrlUtility $object): self
253
    {
254
        $object->conf = $site->getConfiguration();
12✔
255
        $object->variants = $object->conf['baseVariants'] ?? [];
12✔
256

257
        return $object;
12✔
258
    }
259

260
    private function extractConfigurationFromRequest(ServerRequestInterface $request, HeadlessFrontendUrlInterface $object): HeadlessFrontendUrlInterface
261
    {
262
        $site = $request->getAttribute('site');
2✔
263
        if ($site instanceof Site) {
2✔
264
            $object->handleSiteConfiguration($site, $object);
2✔
265
        }
266

267
        $language = $request->getAttribute('language');
2✔
268
        if ($language instanceof SiteLanguage) {
2✔
269
            $object->handleLanguageConfiguration($language, $object);
1✔
270
        }
271

272
        return $object;
2✔
273
    }
274
}
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

© 2024 Coveralls, Inc