Passed
Push — master ( 4751e9...ec72de )
by Rafael
38:16
created

extractParametersFromUriPath()   B

Complexity

Conditions 9
Paths 49

Size

Total Lines 67
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 36
c 1
b 0
f 0
dl 0
loc 67
ccs 0
cts 48
cp 0
rs 8.0555
cc 9
nc 49
nop 3
crap 90

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace ApacheSolrForTypo3\Solr\Middleware;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use ApacheSolrForTypo3\Solr\Routing\RoutingService;
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Psr\Http\Message\UriInterface;
21
use Psr\Http\Server\MiddlewareInterface;
22
use Psr\Http\Server\RequestHandlerInterface;
23
use Psr\Log\LoggerAwareInterface;
24
use Psr\Log\LoggerAwareTrait;
25
use TYPO3\CMS\Core\Routing\SiteRouteResult;
26
use TYPO3\CMS\Core\Site\Entity\NullSite;
27
use TYPO3\CMS\Core\Site\Entity\Site;
28
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
31
/**
32
 * Middleware to create beautiful URLs for Solr
33
 *
34
 * How to use:
35
 * Inside of your extension create following file
36
 * Configuration/RequestMiddlewares.php
37
 *
38
 * return [
39
 *   'frontend' => [
40
 *     'apache-solr-for-typo3/solr-route-enhancer' => [
41
 *       'target' => \ApacheSolrForTypo3\Solr\Middleware\SolrRoutingMiddleware::class,
42
 *       'before' => [
43
 *         'typo3/cms-frontend/site',
44
 *       ]
45
 *     ]
46
 *   ]
47
 * ];
48
 *
49
 * @author Lars Tode <[email protected]>
50
 * @see https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/RequestHandling/Index.html
51
 */
52
class SolrRoutingMiddleware implements MiddlewareInterface, LoggerAwareInterface
53
{
54
    use LoggerAwareTrait;
55
56
    /**
57
     * Solr parameter key
58
     *
59
     * @var string
60
     */
61
    protected $namespace = 'tx_solr';
62
63
    /**
64
     * Settings from enhancer configuration
65
     *
66
     * @var array
67
     */
68
    protected $settings = [];
69
70
    /**
71
     * @var SiteLanguage
72
     */
73
    protected $language = null;
74
75
    /**
76
     * @var RoutingService
77
     */
78
    protected $routingService;
79
80
    /**
81
     * Inject the routing service.
82
     * Used in unit tests too
83
     *
84
     * @param RoutingService $routingService
85
     */
86
    public function injectRoutingService(RoutingService $routingService)
87
    {
88
        $this->routingService = $routingService;
89
    }
90
91
    /**
92
     * Process the request
93
     *
94
     * @param ServerRequestInterface $request
95
     * @param RequestHandlerInterface $handler
96
     * @return ResponseInterface
97
     */
98 1
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
99
    {
100
        /* @var SiteRouteResult $routeResult */
101 1
        $routeResult = $this->getRoutingService()
102 1
            ->getSiteMatcher()
103 1
            ->matchRequest($request);
104
105 1
        $site = $routeResult->getSite();
106
107 1
        if ($site instanceof NullSite) {
108
            return $handler->handle($request);
109
        }
110
111 1
        $this->language = $routeResult->getLanguage();
112
113 1
        if (!($this->language instanceof SiteLanguage)) {
0 ignored issues
show
introduced by
$this->language is always a sub-type of TYPO3\CMS\Core\Site\Entity\SiteLanguage.
Loading history...
114
            return $handler->handle($request);
115
        }
116
117 1
        $page = $this->retrievePageInformation(
118 1
            $request->getUri(),
119 1
            $site
120
        );
121
122 1
        if ((int)$page['uid'] === 0) {
123
            return $handler->handle($request);
124
        }
125
126 1
        $enhancerConfiguration = $this->getEnhancerConfiguration(
127 1
            $site,
128 1
            $this->language->getLanguageId() === 0 ? (int)$page['uid'] : (int)$page['l10n_parent']
129
        );
130
131 1
        if ($enhancerConfiguration === null) {
132 1
            return $handler->handle($request);
133
        }
134
135
        $this->configure($enhancerConfiguration);
136
137
        /*
138
         * Take slug path segments and argument from incoming URI
139
         */
140
        [$slug, $parameters] = $this->extractParametersFromUriPath(
141
            $request->getUri(),
142
            $enhancerConfiguration['routePath'],
143
            (string)$page['slug']
144
        );
145
146
        /*
147
         * Convert path arguments to query arguments
148
         */
149
        if (!empty($parameters)) {
150
            $request = $this->getRoutingService()->addPathArgumentsToQuery(
151
                $request,
152
                $enhancerConfiguration['_arguments'],
153
                $parameters
154
            );
155
        }
156
157
        /*
158
         * Replace internal URI with existing site taken from path information
159
         * We removed a possible path segment from the slug, that again needs to attach.
160
         *
161
         * NOTE: TypoScript is not available at this point!
162
         */
163
        $uri = $request->getUri()->withPath(
164
            $this->getRoutingService()->cleanupHeadingSlash(
165
                $this->language->getBase()->getPath() .
166
                (string)$page['slug']
167
            )
168
        );
169
        $request = $request->withUri($uri);
170
        $queryParams = $request->getQueryParams();
171
        $queryParams = $this->getRoutingService()->unmaskQueryParameters($queryParams);
172
        $queryParams = $this->getRoutingService()->inflateQueryParameter($queryParams);
173
174
        // @todo Drop cHash, but need to recalculate
175
        if (array_key_exists('cHash', $queryParams)) {
176
            unset($queryParams['cHash']);
177
        }
178
179
        $request = $request->withQueryParams($queryParams);
180
        return $handler->handle($request);
181
    }
182
183
    /**
184
     * Configures the middleware by enhancer configuration
185
     *
186
     * @param array $enhancerConfiguration
187
     */
188
    protected function configure(array $enhancerConfiguration): void
189
    {
190
        $this->settings = $enhancerConfiguration['solr'];
191
        $this->namespace = $enhancerConfiguration['extensionKey'] ?? $this->namespace;
192
        $this->routingService = null;
193
    }
194
195
    /**
196
     * Retrieve the enhancer configuration for given site
197
     *
198
     * @param Site $site
199
     * @param int $pageUid
200
     * @return array|null
201
     */
202
    protected function getEnhancerConfiguration(Site $site, int $pageUid): ?array
203
    {
204
        $enhancers = $this->getRoutingService()->fetchEnhancerInSiteConfigurationByPageUid(
205
            $site,
206
            $pageUid
207
        );
208
209
        if (empty($enhancers)) {
210
            return null;
211
        }
212
213
        return $enhancers[0];
214
    }
215
216
    /**
217
     * Extract the slug and all arguments from path
218
     *
219
     * @param UriInterface $uri
220
     * @param string $path
221
     * @param string $pageSlug
222
     * @return array
223
     */
224
    protected function extractParametersFromUriPath(UriInterface $uri, string $path, string $pageSlug): array
225
    {
226
        // URI get path returns the path with given language parameter
227
        // The parameter pageSlug itself does not contains the language parameter.
228
        $uriPath = $this->getRoutingService()->stripLanguagePrefixFromPath(
229
            $this->language,
230
            $uri->getPath()
231
        );
232
233
        if ($uriPath === $pageSlug) {
234
            return [
235
                $pageSlug,
236
                []
237
            ];
238
        }
239
240
        // Remove slug from URI path in order the ensure only the arguments left
241
        if (mb_substr($uriPath, 0, mb_strlen($pageSlug) + 1) === $pageSlug . '/') {
242
            $length = mb_strlen($pageSlug) + 1;
243
            $uriPath = mb_substr($uriPath, $length, mb_strlen($uriPath) - $length);
244
        }
245
246
        // Take care the format of configuration and given slug equals
247
        $uriPath = $this->getRoutingService()->removeHeadingSlash($uriPath);
248
        $path = $this->getRoutingService()->removeHeadingSlash($path);
249
250
        // Remove begin
251
        $uriElements = explode('/', $uriPath);
252
        $routeElements = explode('/', $path);
253
        $slugElements = [];
254
        $arguments = [];
255
        $process = true;
256
        /*
257
         * Extract the slug elements, until the the amount of route elements reached
258
         */
259
        do {
260
            if (count($uriElements) > count($routeElements)) {
261
                $slugElements[] = array_shift($uriElements);
262
            } else {
263
                $process = false;
264
            }
265
        } while ($process);
266
267
        if (empty($routeElements[0])) {
268
            array_shift($routeElements);
269
        }
270
        if (empty($uriElements[0])) {
271
            array_shift($uriElements);
272
        }
273
274
        // Extract the values
275
        $uriElementsCount = count($uriElements);
276
        for ($i = 0; $i < $uriElementsCount; $i++) {
277
            // Skip empty elements
278
            if (empty($uriElements[$i])) {
279
                continue;
280
            }
281
282
            $key = substr($routeElements[$i], 1, strlen($routeElements[$i]) - 1);
283
            $key = substr($key, 0, strlen($key) - 1);
284
285
            $arguments[$key] = $uriElements[$i];
286
        }
287
288
        return [
289
            implode('/', $slugElements),
290
            $arguments
291
        ];
292
    }
293
294
    /**
295
     * Retrieve the page uid to filter the route enhancer
296
     *
297
     * @param UriInterface $uri
298
     * @param Site $site
299
     * @return array
300
     */
301
    protected function retrievePageInformation(UriInterface $uri, Site $site): array
302
    {
303
        $path = $this->getRoutingService()->stripLanguagePrefixFromPath(
304
            $this->language,
305
            $uri->getPath()
306
        );
307
        $slugProvider = $this->getRoutingService()->getSlugCandidateProvider($site);
308
        $scan = true;
309
        $page = [];
310
        do {
311
            $items = $slugProvider->getCandidatesForPath(
312
                $path,
313
                $this->language
314
            );
315
            if (empty($items)) {
316
                $this->logger
317
                    ->/** @scrutinizer ignore-call */
318
                    error(
319
                        vsprintf(
320
                            'Could not determine page for slug "%1$s" and language "%2$s". Given path "%3$s"',
321
                            [
322
                                $path,
323
                                $this->language->getTwoLetterIsoCode(),
324
                                $uri->getPath()
325
                            ]
326
                        )
327
                    );
328
                $scan = false;
329
            } elseif (empty($path)) {
330
                $this->logger
331
                    ->/** @scrutinizer ignore-call */
332
                    error(
333
                        vsprintf(
334
                            'Could resolve page by path "%1$s" and language "%2$s".',
335
                            [
336
                                $uri->getPath(),
337
                                $this->language->getTwoLetterIsoCode()
338
                            ]
339
                        )
340
                    );
341
                $scan = false;
342
            } else {
343
                foreach ($items as $item) {
344
                    $this->logger
345
                        ->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

345
                        ->/** @scrutinizer ignore-call */ 
346
                          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...
346
                            vsprintf(
347
                                'Path "%1$s" -> slug "%2$s"',
348
                                [
349
                                    $path,
350
                                    $item['slug']
351
                                ]
352
                            )
353
                        );
354
355
                    if ($item['slug'] === $path) {
356
                        $page = $item;
357
                        $scan = false;
358
                        break;
359
                    }
360
                }
361
362
                if ($scan) {
363
                    $elements = explode('/', $path);
364
                    if (empty($elements)) {
365
                        $scan = false;
366
                    } else {
367
                        array_pop($elements);
368
                        $path = implode('/', $elements);
369
                    }
370
                }
371
            }
372
        } while($scan);
373
        return $page;
374
    }
375
376
    /**
377
     * @return RoutingService
378
     */
379
    protected function getRoutingService(): RoutingService
380
    {
381
        if (null === $this->routingService) {
382
            $this->routingService = GeneralUtility::makeInstance(
383
                RoutingService::class,
384
                $this->settings,
385
                $this->namespace
386
            );
387
        } else {
388
            $this->routingService = $this->routingService->withSettings($this->settings);
389
        }
390
        return $this->routingService;
391
    }
392
}
393