Test Failed
Pull Request — release/2.x (#61)
by
unknown
21:54 queued 12:25
created

Listener::onKernelRequest()   F

Complexity

Conditions 24
Paths 544

Size

Total Lines 94
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 24.6515

Importance

Changes 8
Bugs 1 Features 0
Metric Value
cc 24
eloc 61
c 8
b 1
f 0
nc 544
nop 1
dl 0
loc 94
ccs 43
cts 48
cp 0.8958
crap 24.6515
rs 0.6333

How to fix   Long Method    Complexity   

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
/**
3
 * @author Gerard van Helden <[email protected]>
4
 * @copyright Zicht Online <http://zicht.nl>
5
 */
6
7
namespace Zicht\Bundle\UrlBundle\Aliasing;
8
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpFoundation\Response;
11
use Symfony\Component\HttpKernel\Event;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpKernel\EventListener\RouterListener;
14
use Symfony\Component\HttpKernel\HttpKernelInterface;
15
use Zicht\Bundle\UrlBundle\Aliasing\Mapper\UrlMapperInterface;
16
use Zicht\Bundle\UrlBundle\Url\Params\UriParser;
17
use Zicht\Bundle\UrlBundle\Entity\UrlAlias;
18
19
/**
20
 * Listens to incoming and outgoing requests to handle url aliasing at the kernel master request level.
21
 */
22
class Listener
23
{
24
    const SLASH_SUFFIX_ABSTAIN = 'abstain';
25
    const SLASH_SUFFIX_ACCEPT = 'accept';
26
    const SLASH_SUFFIX_REDIRECT_PERM = 'redirect-301';
27
    const SLASH_SUFFIX_REDIRECT_TEMP = 'redirect-302';
28
29
    /** @var Aliasing */
30
    protected $aliasing;
31
32
    /** @var RouterListener */
33
    protected $router;
34
35 11
    /** @var string[] */
36
    protected $excludePatterns = array();
37 11
38 11
    /** @var bool */
39 11
    protected $isParamsEnabled = false;
40
41
    /** @var string|null */
42
    protected $slashSuffixHandling = self::SLASH_SUFFIX_ABSTAIN;
43
44
    /**
45
     * Construct the aliasing listener.
46
     *
47
     * @param Aliasing $aliasing
48
     * @param RouterListener $router
49
     */
50
    public function __construct(Aliasing $aliasing, RouterListener $router)
51
    {
52
        $this->aliasing = $aliasing;
53
        $this->router = $router;
54
    }
55
56
    /**
57
     * Listens to redirect responses, to replace any internal url with a public one.
58
     *
59
     * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $e
60
     * @return void
61
     */
62
    public function onKernelResponse(Event\FilterResponseEvent $e)
63
    {
64
        if ($e->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
65
            $response = $e->getResponse();
66
67
            // only do anything if the response has a Location header
68
            if (false !== ($location = $response->headers->get('location', false))) {
0 ignored issues
show
introduced by
The condition false !== $location = $r...>get('location', false) is always true.
Loading history...
Bug introduced by
false of type false is incompatible with the type null|string|string[] expected by parameter $default of Symfony\Component\HttpFoundation\HeaderBag::get(). ( Ignorable by Annotation )

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

68
            if (false !== ($location = $response->headers->get('location', /** @scrutinizer ignore-type */ false))) {
Loading history...
69
                $absolutePrefix = $e->getRequest()->getSchemeAndHttpHost();
70
71
                if (parse_url($location, PHP_URL_SCHEME)) {
72
                    if (substr($location, 0, strlen($absolutePrefix)) === $absolutePrefix) {
73
                        $relative = substr($location, strlen($absolutePrefix));
74
                    } else {
75
                        $relative = null;
76
                    }
77
                } else {
78
                    $relative = $location;
79
                }
80
81
                // Possible suffix for the rewrite URL
82
                $suffix = '';
83
84
                /**
85
                 * Catches the following situation:
86
                 *
87
                 * Some redirect URLs might contain extra URL parameters in the form of:
88
                 *
89
                 *      /nl/page/666/terms=FOO/tag=BAR
90
                 *
91
                 * (E.g. some SOLR implementations use this URL scheme)
92
                 *
93
                 * The relative URL above is then incorrect and the public alias can not be found.
94
                 *
95
                 * Remove /terms=FOO/tag=BAR from the relative path and attach to clean URL if found.
96
                 *
97
                 */
98
                if (preg_match('/^(\/[a-z]{2,2}\/page\/\d+)(.*)$/', $relative, $matches)) {
99
                    list(, $relative, $suffix) = $matches;
100
                } elseif (preg_match('/^(\/page\/\d+)(.*)$/', $relative, $matches)) {
101
                    /* For old sites that don't have the locale in the URI */
102
                    list(, $relative, $suffix) = $matches;
103
                }
104
105
                if (null !== $relative && null !== ($url = $this->aliasing->hasPublicAlias($relative))) {
106 2
                    $rewrite = $absolutePrefix . $url . $suffix;
107
                    $response->headers->set('location', $rewrite);
108 2
                }
109 2
            }
110
111
            $this->rewriteResponse($e->getRequest(), $response);
112
        }
113
    }
114
115
    /**
116
     * Exclude patterns from aliasing
117 3
     *
118
     * @param array $excludePatterns
119 3
     * @return void
120 3
     */
121
    public function setExcludePatterns($excludePatterns)
122
    {
123
        $this->excludePatterns = $excludePatterns;
124
    }
125
126
    /**
127
     * Whether or not to consider URL parameters (key/value pairs at the end of the URL)
128 10
     *
129
     * @param bool $isParamsEnabled
130 10
     * @return void
131 10
     */
132 2
    public function setIsParamsEnabled($isParamsEnabled)
133 1
    {
134 2
        $this->isParamsEnabled = $isParamsEnabled;
135
    }
136
137 10
    /**
138
     * @param string $slashSuffixHandling
139
     */
140
    public function setSlashSuffixHandling($slashSuffixHandling)
141
    {
142
        $this->slashSuffixHandling = $slashSuffixHandling;
143
    }
144
145
    /**
146
     * Returns true if the URL matches any of the exclude patterns
147
     *
148 11
     * @param string $url
149
     * @return bool
150 11
     */
151 10
    protected function isExcluded($url)
152 10
    {
153
        foreach ($this->excludePatterns as $pattern) {
154 10
            if (preg_match($pattern, $url)) {
155
                return true;
156 1
            }
157
        }
158
159 9
        return false;
160 3
    }
161
162
    /**
163
     * Listens to master requests and translates the URL to an internal url, if there is an alias available
164
     *
165 3
     * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
166
     * @return void
167
     * @throws \UnexpectedValueException
168 3
     */
169 3
    public function onKernelRequest(Event\GetResponseEvent $event)
170 3
    {
171 2
        if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
172
            return;
173 3
        }
174 2
175
        $request = $event->getRequest();
176 2
        $publicUrl = rawurldecode($request->getRequestUri());
177 2
178
        if ($this->isExcluded($publicUrl)) {
179 2
            // don't process urls which are marked as excluded.
180 1
            return;
181
        }
182 1
183
        $queryString = '';
184
        if (false !== ($queryMark = strpos($publicUrl, '?'))) {
185
            $queryString = substr($publicUrl, $queryMark);
186
            $publicUrl = substr($publicUrl, 0, $queryMark);
187
        }
188 8
189 6
        if ($this->isParamsEnabled) {
190 6
            $parts = explode('/', $publicUrl);
191 3
            $params = array();
192 3
            while (false !== strpos(end($parts), '=')) {
193 3
                array_push($params, array_pop($parts));
194 2
            }
195 2
            if ($params) {
196 2
                $publicUrl = join('/', $parts);
197
198 1
                $parser = new UriParser();
199 1
                $request->query->add($parser->parseUri(join('/', array_reverse($params))));
200 1
201 1
                if (!$this->aliasing->hasInternalAlias($publicUrl, false)) {
202 6
                    $this->rewriteRequest($event, $publicUrl . $queryString);
203
204
                    return;
205
                }
206 2
            }
207
        }
208
209
        $tryPublicUrls = [$publicUrl => null];
210
        if ($queryString !== '') {
211
            $tryPublicUrls[$publicUrl . $queryString] = null;
212
        }
213
        if ($this->slashSuffixHandling !== static::SLASH_SUFFIX_ABSTAIN && substr($publicUrl, -1) === '/' && rtrim($publicUrl, '/') !== '') {
214
            $tryPublicUrls[rtrim($publicUrl, '/')] = $this->slashSuffixHandling;
215
            if ($queryString !== '') {
216
                $tryPublicUrls[rtrim($publicUrl, '/') . $queryString] = $this->slashSuffixHandling;
217 8
            }
218
        }
219
220
        $qb = $this->aliasing->getRepository()->createQueryBuilder('u');
221
        $qb->where($qb->expr()->in('u.public_url', array_keys($tryPublicUrls)))
222
            ->indexBy('u', 'u.public_url');
223
        /** @var UrlAlias[] $urlAliases */
224
        $urlAliases = $qb->getQuery()->getResult();
225
226
        if (count($urlAliases) === 0) {
227 4
            return;
228
        }
229
230 4
        foreach ($tryPublicUrls as $tryPublicUrl => $handlingMode) {
231 4
            if (!array_key_exists($tryPublicUrl, $urlAliases)) {
232 4
                continue;
233 4
            }
234 4
235 4
            $urlAlias = $urlAliases[$tryPublicUrl];
236
237 4
            switch ($handlingMode) {
238 4
                case static::SLASH_SUFFIX_REDIRECT_TEMP:
239 4
                    $url = $urlAlias->getMode() === UrlAlias::ALIAS ? $urlAlias->getInternalUrl() : $urlAlias->getPublicUrl(); // Same mode? Use internal URL directly, don't go redirecting twice...
240 4
                    $event->setResponse(new RedirectResponse($url, Response::HTTP_FOUND));
241
                    break;
242
243
                case static::SLASH_SUFFIX_REDIRECT_PERM:
244 4
                    $url = $urlAlias->getMode() === UrlAlias::MOVE ? $urlAlias->getInternalUrl() : $urlAlias->getPublicUrl(); // Same mode? Use internal URL directly, don't go redirecting twice...
245 4
                    $event->setResponse(new RedirectResponse($url, Response::HTTP_MOVED_PERMANENTLY));
246 4
                    break;
247 4
248
                case static::SLASH_SUFFIX_ACCEPT:
249 4
                    // Continue as if the correct URL (without '/' suffix) was requested. Could result in duplicate content disqualifications
250 4
                default: // $handlingMode === null
251
                    switch ($urlAlias->getMode()) {
252
                        case UrlAlias::REWRITE:
253
                            $this->rewriteRequest($event, $urlAlias->getInternalUrl());
254
                            break;
255
                        case UrlAlias::MOVE:
256
                        case UrlAlias::ALIAS:
257
                            $event->setResponse(new RedirectResponse($urlAlias->getInternalUrl(), $urlAlias->getMode()));
258
                            break;
259
                        default:
260
                            throw new \UnexpectedValueException(sprintf("Invalid mode %s for UrlAlias %s.", $urlAlias->getMode(), json_encode($urlAlias)));
261
                    }
262
                break;
263
            }
264
        }
265
    }
266
267
    /**
268
     * Route the request to the specified URL.
269
     *
270
     * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
271
     * @param string $url
272
     * @return void
273
     */
274
    public function rewriteRequest($event, $url)
275
    {
276
        // override the request's REQUEST_URI
277
        $event->getRequest()->initialize(
278
            $event->getRequest()->query->all(),
279
            $event->getRequest()->request->all(),
280
            $event->getRequest()->attributes->all(),
281
            $event->getRequest()->cookies->all(),
282
            $event->getRequest()->files->all(),
283
            array(
284
                'ORIGINAL_REQUEST_URI' => $event->getRequest()->server->get('REQUEST_URI'),
285
                'REQUEST_URI' => $url
286
            ) + $event->getRequest()->server->all(),
287
            $event->getRequest()->getContent()
288
        );
289
290
        // route the request
291
        $subEvent = new Event\GetResponseEvent(
292
            $event->getKernel(),
293
            $event->getRequest(),
294
            $event->getRequestType()
295
        );
296
        $this->router->onKernelRequest($subEvent);
297
    }
298
299
    /**
300
     * Rewrite URL's from internal naming to public aliases in the response.
301
     *
302
     * @param Request $request
303
     * @param Response $response
304
     * @return void
305
     */
306
    protected function rewriteResponse(Request $request, Response $response)
307
    {
308
        // for debugging purposes. Might need to be configurable.
309
        if ($request->query->get('__disable_aliasing')) {
310
            return;
311
        }
312
        if (preg_match('!^/admin/!', $request->getRequestUri())) {
313
            // don't bother here.
314
            return;
315
        }
316
317
        if ($response->getContent()) {
318
            $contentType = current(explode(';', $response->headers->get('content-type', 'text/html')));
319
            $response->setContent(
320
                $this->aliasing->mapContent(
321
                    $contentType,
322
                    UrlMapperInterface::MODE_INTERNAL_TO_PUBLIC,
323
                    $response->getContent(),
324
                    [$request->getHttpHost()]
325
                )
326
            );
327
        }
328
    }
329
}
330