Test Failed
Push — release/3.x ( 447600...4272fa )
by
unknown
02:22 queued 10s
created

Listener::setExcludePatterns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
    public function __construct(Aliasing $aliasing, RouterListener $router)
36
    {
37
        $this->aliasing = $aliasing;
38
        $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
    }
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
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 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...
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
    public function setExcludePatterns($excludePatterns)
107
    {
108
        $this->excludePatterns = $excludePatterns;
109
    }
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
    public function setIsParamsEnabled($isParamsEnabled)
118
    {
119
        $this->isParamsEnabled = $isParamsEnabled;
120
    }
121
122
    /**
123
     * Returns true if the URL matches any of the exclude patterns
124
     *
125
     * @param string $url
126
     * @return bool
127
     */
128
    protected function isExcluded($url)
129
    {
130
        $ret = false;
131
        foreach ($this->excludePatterns as $pattern) {
132
            if (preg_match($pattern, $url)) {
133
                $ret = true;
134
                break;
135
            }
136
        }
137
        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
    public function onKernelRequest(Event\GetResponseEvent $event)
149
    {
150
        if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
151
            $request = $event->getRequest();
152
            $publicUrl = $request->getRequestUri();
153
154
            if ($this->isExcluded($publicUrl)) {
155
                // don't process urls which are marked as excluded.
156
                return;
157
            }
158
159
            if ($this->isParamsEnabled) {
160
                if (false !== ($queryMark = strpos($publicUrl, '?'))) {
161
                    $originalUrl = $publicUrl;
162
                    $publicUrl = substr($originalUrl, 0, $queryMark);
163
                    $queryString = substr($originalUrl, $queryMark);
164
                } else {
165
                    $queryString = null;
166
                }
167
168
                $parts = explode('/', $publicUrl);
169
                $params = array();
170
                while (false !== strpos(end($parts), '=')) {
171
                    array_push($params, array_pop($parts));
172
                }
173
                if ($params) {
174
                    $publicUrl = join('/', $parts);
175
176
                    $parser = new UriParser();
177
                    $request->query->add($parser->parseUri(join('/', array_reverse($params))));
178
179
                    if (!$this->aliasing->hasInternalAlias($publicUrl, false)) {
180
                        $this->rewriteRequest($event, $publicUrl . $queryString);
181
182
                        return;
183
                    }
184
                }
185
            }
186
187
            /** @var UrlAlias $url */
188
            if ($url = $this->aliasing->hasInternalAlias($publicUrl, true)) {
189
                switch ($url->getMode()) {
190
                    case UrlAlias::REWRITE:
191
                        $this->rewriteRequest($event, $url->getInternalUrl());
192
                        break;
193
                    case UrlAlias::MOVE:
194
                    case UrlAlias::ALIAS:
195
                        $event->setResponse(new RedirectResponse($url->getInternalUrl(), $url->getMode()));
196
                        break;
197
                    default:
198
                        throw new \UnexpectedValueException(
199
                            sprintf(
200
                                "Invalid mode %s for UrlAlias %s.",
201
                                $url->getMode(),
202
                                json_encode($url)
203
                            )
204
                        );
205
                }
206
            } 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
    }
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
    public function rewriteRequest($event, $url)
228
    {
229
        // override the request's REQUEST_URI
230
        $event->getRequest()->initialize(
231
            $event->getRequest()->query->all(),
232
            $event->getRequest()->request->all(),
233
            $event->getRequest()->attributes->all(),
234
            $event->getRequest()->cookies->all(),
235
            $event->getRequest()->files->all(),
236
            array(
237
                'ORIGINAL_REQUEST_URI' => $event->getRequest()->server->get('REQUEST_URI'),
238
                'REQUEST_URI' => $url
239
            ) + $event->getRequest()->server->all(),
240
            $event->getRequest()->getContent()
241
        );
242
243
        // route the request
244
        $subEvent = new Event\GetResponseEvent(
245
            $event->getKernel(),
246
            $event->getRequest(),
247
            $event->getRequestType()
248
        );
249
        $this->router->onKernelRequest($subEvent);
250
    }
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