Listener::onKernelRequest()   C
last analyzed

Complexity

Conditions 14
Paths 39

Size

Total Lines 66
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 14.9079

Importance

Changes 8
Bugs 1 Features 0
Metric Value
cc 14
eloc 43
c 8
b 1
f 0
nc 39
nop 1
dl 0
loc 66
ccs 35
cts 42
cp 0.8333
crap 14.9079
rs 6.2666

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
    protected $aliasing;
25
26
    protected $excludePatterns = array();
27
    protected $isParamsEnabled = false;
28
29
    /**
30
     * Construct the aliasing listener.
31
     *
32
     * @param Aliasing $aliasing
33
     * @param RouterListener $router
34
     */
35 11
    public function __construct(Aliasing $aliasing, RouterListener $router)
36
    {
37 11
        $this->aliasing = $aliasing;
38 11
        $this->router = $router;
0 ignored issues
show
Bug Best Practice introduced by
The property router does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
39 11
    }
40
41
    /**
42
     * Listens to redirect responses, to replace any internal url with a public one.
43
     *
44
     * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $e
45
     * @return void
46
     */
47
    public function onKernelResponse(Event\FilterResponseEvent $e)
48
    {
49
        if ($e->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
50
            $response = $e->getResponse();
51
52
            // only do anything if the response has a Location header
53
            if (false !== ($location = $response->headers->get('location', false))) {
0 ignored issues
show
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

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