Passed
Push — release-11.5.x ( 385fe8...cd49eb )
by Rafael
53:22 queued 14:05
created

SolrRoutingMiddleware::getEnhancerConfiguration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
dl 0
loc 12
ccs 0
cts 8
cp 0
rs 10
c 1
b 0
f 0
cc 2
nc 2
nop 2
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace ApacheSolrForTypo3\Solr\Middleware;
19
20
use ApacheSolrForTypo3\Solr\IndexQueue\PageIndexerRequest;
21
use ApacheSolrForTypo3\Solr\Routing\RoutingService;
22
use Psr\Http\Message\ResponseInterface;
23
use Psr\Http\Message\ServerRequestInterface;
24
use Psr\Http\Message\UriInterface;
25
use Psr\Http\Server\MiddlewareInterface;
26
use Psr\Http\Server\RequestHandlerInterface;
27
use Psr\Log\LoggerAwareInterface;
28
use Psr\Log\LoggerAwareTrait;
29
use TYPO3\CMS\Core\Routing\SiteRouteResult;
30
use TYPO3\CMS\Core\Site\Entity\NullSite;
31
use TYPO3\CMS\Core\Site\Entity\Site;
32
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
35
/**
36
 * Middleware to create beautiful URLs for Solr
37
 *
38
 * How to use:
39
 * Inside your extension create following file
40
 * Configuration/RequestMiddlewares.php
41
 *
42
 * return [
43
 *   'frontend' => [
44
 *     'apache-solr-for-typo3/solr-route-enhancer' => [
45
 *       'target' => \ApacheSolrForTypo3\Solr\Middleware\SolrRoutingMiddleware::class,
46
 *       'before' => [
47
 *         'typo3/cms-frontend/site',
48
 *       ]
49
 *     ]
50
 *   ]
51
 * ];
52
 *
53
 * @author Lars Tode <[email protected]>
54
 * @see https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/RequestHandling/Index.html
55
 */
56
class SolrRoutingMiddleware implements MiddlewareInterface, LoggerAwareInterface
57
{
58
    use LoggerAwareTrait;
59
60
    /**
61
     * Solr parameter key
62
     *
63
     * @var string
64
     */
65
    protected string $namespace = 'tx_solr';
66
67
    /**
68
     * Settings from enhancer configuration
69
     *
70
     * @var array
71
     */
72
    protected array $settings = [];
73
74
    /**
75
     * @var SiteLanguage|null
76
     */
77
    protected ?SiteLanguage $language;
78
79
    /**
80
     * @var RoutingService|null
81
     */
82
    protected ?RoutingService $routingService = null;
83
84
    /**
85
     * Inject the routing service.
86
     * Used in unit tests too
87
     *
88
     * @param RoutingService $routingService
89
     */
90
    public function injectRoutingService(RoutingService $routingService)
91
    {
92
        $this->routingService = $routingService;
93
    }
94
95
    /**
96
     * Process the request
97
     *
98
     * @param ServerRequestInterface $request
99
     * @param RequestHandlerInterface $handler
100
     * @return ResponseInterface
101
     */
102 39
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
103
    {
104 39
        if (!$request->hasHeader(PageIndexerRequest::SOLR_INDEX_HEADER)) {
105 38
            return $handler->handle($request);
106
        }
107
108
        /* @var SiteRouteResult $routeResult */
109 1
        $routeResult = $this->getRoutingService()
110 1
            ->getSiteMatcher()
111 1
            ->matchRequest($request);
112
113 1
        $site = $routeResult->getSite();
114
115 1
        if ($site instanceof NullSite) {
116
            return $handler->handle($request);
117
        }
118
119 1
        $this->language = $routeResult->getLanguage();
120
121 1
        if (!($this->language instanceof SiteLanguage)) {
122
            return $handler->handle($request);
123
        }
124
125 1
        $page = $this->retrievePageInformation(
126 1
            $request->getUri(),
127 1
            $site
128 1
        );
129
130 1
        if (empty($page['uid'])) {
131
            return $handler->handle($request);
132
        }
133
134 1
        $enhancerConfiguration = $this->getEnhancerConfiguration(
135 1
            $site,
136 1
            $this->language->getLanguageId() === 0 ? (int)$page['uid'] : (int)$page['l10n_parent']
137 1
        );
138
139 1
        if ($enhancerConfiguration === null) {
140 1
            return $handler->handle($request);
141
        }
142
143
        $this->configure($enhancerConfiguration);
144
145
        /*
146
         * Take slug path segments and argument from incoming URI
147
         */
148
        [$slug, $parameters] = $this->extractParametersFromUriPath(
149
            $request->getUri(),
150
            $enhancerConfiguration['routePath'],
151
            $page['slug'] ?? ''
152
        );
153
154
        /*
155
         * Convert path arguments to query arguments
156
         */
157
        if (!empty($parameters)) {
158
            $request = $this->getRoutingService()->addPathArgumentsToQuery(
159
                $request,
160
                $enhancerConfiguration['_arguments'],
161
                $parameters
162
            );
163
        }
164
165
        /*
166
         * Replace internal URI with existing site taken from path information
167
         * We removed a possible path segment from the slug, that again needs to attach.
168
         *
169
         * NOTE: TypoScript is not available at this point!
170
         */
171
        $uri = $request->getUri()->withPath(
172
            $this->getRoutingService()->cleanupHeadingSlash(
173
                $this->language->getBase()->getPath() .
174
                $page['slug'] ?? ''
175
            )
176
        );
177
        $request = $request->withUri($uri);
178
        $queryParams = $request->getQueryParams();
179
        $queryParams = $this->getRoutingService()->unmaskQueryParameters($queryParams);
180
        $queryParams = $this->getRoutingService()->inflateQueryParameter($queryParams);
181
182
        // @todo Drop cHash, but need to recalculate
183
        if (array_key_exists('cHash', $queryParams)) {
184
            unset($queryParams['cHash']);
185
        }
186
187
        $request = $request->withQueryParams($queryParams);
188
        return $handler->handle($request);
189
    }
190
191
    /**
192
     * Configures the middleware by enhancer configuration
193
     *
194
     * @param array $enhancerConfiguration
195
     */
196
    protected function configure(array $enhancerConfiguration): void
197
    {
198
        $this->settings = $enhancerConfiguration['solr'];
199
        $this->namespace = $enhancerConfiguration['extensionKey'] ?? $this->namespace;
200
        $this->routingService = null;
201
    }
202
203
    /**
204
     * Retrieve the enhancer configuration for given site
205
     *
206
     * @param Site $site
207
     * @param int $pageUid
208
     * @return array|null
209
     */
210
    protected function getEnhancerConfiguration(Site $site, int $pageUid): ?array
211
    {
212
        $enhancers = $this->getRoutingService()->fetchEnhancerInSiteConfigurationByPageUid(
213
            $site,
214
            $pageUid
215
        );
216
217
        if (empty($enhancers)) {
218
            return null;
219
        }
220
221
        return $enhancers[0];
222
    }
223
224
    /**
225
     * Extract the slug and all arguments from path
226
     *
227
     * @param UriInterface $uri
228
     * @param string $path
229
     * @param string $pageSlug
230
     * @return array
231
     */
232
    protected function extractParametersFromUriPath(UriInterface $uri, string $path, string $pageSlug): array
233
    {
234
        // URI get path returns the path with given language parameter
235
        // The parameter pageSlug itself does not contain the language parameter.
236
        $uriPath = $this->getRoutingService()->stripLanguagePrefixFromPath(
237
            $this->language,
0 ignored issues
show
Bug introduced by
It seems like $this->language can also be of type null; however, parameter $language of ApacheSolrForTypo3\Solr\...anguagePrefixFromPath() does only seem to accept TYPO3\CMS\Core\Site\Entity\SiteLanguage, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

237
            /** @scrutinizer ignore-type */ $this->language,
Loading history...
238
            $uri->getPath()
239
        );
240
241
        if ($uriPath === $pageSlug) {
242
            return [
243
                $pageSlug,
244
                [],
245
            ];
246
        }
247
248
        // Remove slug from URI path in order to ensure only the arguments left
249
        if (mb_substr($uriPath, 0, mb_strlen($pageSlug) + 1) === $pageSlug . '/') {
250
            $length = mb_strlen($pageSlug) + 1;
251
            $uriPath = mb_substr($uriPath, $length, mb_strlen($uriPath) - $length);
252
        }
253
254
        // Take care the format of configuration and given slug equals
255
        $uriPath = $this->getRoutingService()->removeHeadingSlash($uriPath);
256
        $path = $this->getRoutingService()->removeHeadingSlash($path);
257
258
        // Remove begin
259
        $uriElements = explode('/', $uriPath);
260
        $routeElements = explode('/', $path);
261
        $slugElements = [];
262
        $arguments = [];
263
        $process = true;
264
        /*
265
         * Extract the slug elements, until the amount of route elements reached
266
         */
267
        do {
268
            if (count($uriElements) > count($routeElements)) {
269
                $slugElements[] = array_shift($uriElements);
270
            } else {
271
                $process = false;
272
            }
273
        } while ($process);
274
275
        if (empty($routeElements[0])) {
276
            array_shift($routeElements);
277
        }
278
        if (empty($uriElements[0])) {
279
            array_shift($uriElements);
280
        }
281
282
        // Extract the values
283
        $uriElementsCount = count($uriElements);
284
        for ($i = 0; $i < $uriElementsCount; $i++) {
285
            // Skip empty elements
286
            if (empty($uriElements[$i])) {
287
                continue;
288
            }
289
290
            $key = substr($routeElements[$i], 1, strlen($routeElements[$i]) - 1);
291
            $key = substr($key, 0, strlen($key) - 1);
292
293
            $arguments[$key] = $uriElements[$i];
294
        }
295
296
        return [
297
            implode('/', $slugElements),
298
            $arguments,
299
        ];
300
    }
301
302
    /**
303
     * Retrieve the page uid to filter the route enhancer
304
     *
305
     * @param UriInterface $uri
306
     * @param Site $site
307
     * @return array
308
     */
309
    protected function retrievePageInformation(UriInterface $uri, Site $site): array
310
    {
311
        $path = $this->getRoutingService()->stripLanguagePrefixFromPath(
312
            $this->language,
0 ignored issues
show
Bug introduced by
It seems like $this->language can also be of type null; however, parameter $language of ApacheSolrForTypo3\Solr\...anguagePrefixFromPath() does only seem to accept TYPO3\CMS\Core\Site\Entity\SiteLanguage, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

312
            /** @scrutinizer ignore-type */ $this->language,
Loading history...
313
            $uri->getPath()
314
        );
315
        $slugProvider = $this->getRoutingService()->getSlugCandidateProvider($site);
316
        $scan = true;
317
        $page = [];
318
        do {
319
            $items = $slugProvider->getCandidatesForPath(
320
                $path,
321
                $this->language
0 ignored issues
show
Bug introduced by
It seems like $this->language can also be of type null; however, parameter $language of TYPO3\CMS\Core\Routing\P...:getCandidatesForPath() does only seem to accept TYPO3\CMS\Core\Site\Entity\SiteLanguage, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

321
                /** @scrutinizer ignore-type */ $this->language
Loading history...
322
            );
323
            if (empty($items)) {
324
                $this->logger
325
                    ->/** @scrutinizer ignore-call */
326
                    error(
327
                        vsprintf(
328
                            'Could not determine page for slug "%1$s" and language "%2$s". Given path "%3$s"',
329
                            [
330
                                $path,
331
                                $this->language->getTwoLetterIsoCode(),
0 ignored issues
show
Bug introduced by
The method getTwoLetterIsoCode() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

331
                                $this->language->/** @scrutinizer ignore-call */ 
332
                                                 getTwoLetterIsoCode(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
332
                                $uri->getPath(),
333
                            ]
334
                        )
335
                    );
336
                $scan = false;
337
            } elseif (empty($path)) {
338
                $this->logger
339
                    ->/** @scrutinizer ignore-call */
340
                    error(
341
                        vsprintf(
342
                            'Could not resolve page by path "%1$s" and language "%2$s".',
343
                            [
344
                                $uri->getPath(),
345
                                $this->language->getTwoLetterIsoCode(),
346
                            ]
347
                        )
348
                    );
349
                $scan = false;
350
            } else {
351
                foreach ($items as $item) {
352
                    $this->logger
353
                        ->info(
0 ignored issues
show
Bug introduced by
The method info() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

353
                        ->/** @scrutinizer ignore-call */ 
354
                          info(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
354
                            vsprintf(
355
                                'Path "%1$s" -> slug "%2$s"',
356
                                [
357
                                    $path,
358
                                    $item['slug'],
359
                                ]
360
                            )
361
                        );
362
363
                    if ($item['slug'] === $path) {
364
                        $page = $item;
365
                        $scan = false;
366
                        break;
367
                    }
368
                }
369
370
                if ($scan) {
371
                    $elements = explode('/', $path);
372
                    if (empty($elements)) {
373
                        $scan = false;
374
                    } else {
375
                        array_pop($elements);
376
                        $path = implode('/', $elements);
377
                    }
378
                }
379
            }
380
        } while ($scan);
381
        return $page;
382
    }
383
384
    /**
385
     * @return RoutingService
386
     */
387
    protected function getRoutingService(): RoutingService
388
    {
389
        if ($this->routingService === null) {
390
            $this->routingService = GeneralUtility::makeInstance(
391
                RoutingService::class,
392
                $this->settings,
393
                $this->namespace
394
            );
395
        } else {
396
            $this->routingService = $this->routingService->withSettings($this->settings);
397
        }
398
        return $this->routingService;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->routingService could return the type null which is incompatible with the type-hinted return ApacheSolrForTypo3\Solr\Routing\RoutingService. Consider adding an additional type-check to rule them out.
Loading history...
399
    }
400
}
401