Completed
Push — EZP-29891 ( 916cf6...0402ff )
by
unknown
16:53
created

UrlAliasRouter::matchRequest()   C

Complexity

Conditions 15
Paths 81

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
nc 81
nop 1
dl 0
loc 85
rs 5.0606
c 0
b 0
f 0

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
/**
4
 * File containing the UrlAliasRouter class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\MVC\Symfony\Routing;
10
11
use eZ\Publish\API\Repository\LocationService;
12
use eZ\Publish\API\Repository\URLAliasService;
13
use eZ\Publish\API\Repository\ContentService;
14
use eZ\Publish\API\Repository\Values\Content\URLAlias;
15
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
16
use eZ\Publish\API\Repository\Values\Content\Location;
17
use eZ\Publish\Core\MVC\Symfony\View\Manager as ViewManager;
18
use eZ\Publish\Core\MVC\Symfony\Routing\Generator\UrlAliasGenerator;
19
use Symfony\Cmf\Component\Routing\ChainedRouterInterface;
20
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
21
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\Routing\RequestContext;
24
use Psr\Log\LoggerInterface;
25
use Symfony\Component\Routing\RouteCollection;
26
use Symfony\Component\Routing\Route as SymfonyRoute;
27
use Symfony\Component\Routing\Exception\RouteNotFoundException;
28
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
29
use InvalidArgumentException;
30
use LogicException;
31
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
32
33
class UrlAliasRouter implements ChainedRouterInterface, RequestMatcherInterface
34
{
35
    const URL_ALIAS_ROUTE_NAME = 'ez_urlalias';
36
37
    /**
38
     * @deprecated since 6.0.0.
39
     */
40
    const LOCATION_VIEW_CONTROLLER = 'ez_content:viewLocation';
41
42
    /**
43
     * @since 6.0.0
44
     */
45
    const VIEW_ACTION = 'ez_content:viewAction';
46
47
    /**
48
     * @var \Symfony\Component\Routing\RequestContext
49
     */
50
    protected $requestContext;
51
52
    /**
53
     * @var \eZ\Publish\API\Repository\LocationService
54
     */
55
    protected $locationService;
56
57
    /**
58
     * @var \eZ\Publish\API\Repository\URLAliasService
59
     */
60
    protected $urlAliasService;
61
62
    /**
63
     * @var \eZ\Publish\API\Repository\ContentService
64
     */
65
    protected $contentService;
66
67
    /**
68
     * @var \eZ\Publish\Core\MVC\Symfony\Routing\Generator\UrlAliasGenerator
69
     */
70
    protected $generator;
71
72
    /**
73
     * Holds current root Location id.
74
     *
75
     * @var int|string
76
     */
77
    protected $rootLocationId;
78
79
    /**
80
     * @var \Psr\Log\LoggerInterface
81
     */
82
    protected $logger;
83
84
    public function __construct(
85
        LocationService $locationService,
86
        URLAliasService $urlAliasService,
87
        ContentService $contentService,
88
        UrlAliasGenerator $generator,
89
        RequestContext $requestContext,
90
        LoggerInterface $logger = null
91
    ) {
92
        $this->locationService = $locationService;
93
        $this->urlAliasService = $urlAliasService;
94
        $this->contentService = $contentService;
95
        $this->generator = $generator;
96
        $this->requestContext = $requestContext !== null ? $requestContext : new RequestContext();
97
        $this->logger = $logger;
98
    }
99
100
    /**
101
     * Injects current root Location id.
102
     *
103
     * @param int|string $rootLocationId
104
     */
105
    public function setRootLocationId($rootLocationId)
106
    {
107
        $this->rootLocationId = $rootLocationId;
108
    }
109
110
    /**
111
     * Tries to match a request with a set of routes.
112
     *
113
     * If the matcher can not find information, it must throw one of the exceptions documented
114
     * below.
115
     *
116
     * @param Request $request The request to match
117
     *
118
     * @return array An array of parameters
119
     *
120
     * @throws \Symfony\Component\Routing\Exception\ResourceNotFoundException If no matching resource could be found
121
     */
122
    public function matchRequest(Request $request)
123
    {
124
        try {
125
            $requestedPath = $request->attributes->get('semanticPathinfo', $request->getPathInfo());
126
            $urlAlias = $this->getUrlAlias($requestedPath);
127
            if ($this->rootLocationId === null) {
128
                $pathPrefix = '/';
129
            } else {
130
                $pathPrefix = $this->generator->getPathPrefixByRootLocationId($this->rootLocationId);
131
            }
132
133
            $params = array(
134
                '_route' => self::URL_ALIAS_ROUTE_NAME,
135
            );
136
            switch ($urlAlias->type) {
137
                case URLAlias::LOCATION:
138
                    $location = $this->generator->loadLocation($urlAlias->destination);
139
                    $params += array(
140
                        '_controller' => static::VIEW_ACTION,
141
                        'contentId' => $location->contentId,
142
                        'locationId' => $urlAlias->destination,
143
                        'viewType' => ViewManager::VIEW_TYPE_FULL,
144
                        'layout' => true,
145
                    );
146
147
                    $request->attributes->set('locationId', $urlAlias->destination);
148
149
                    // For Location alias setup 301 redirect to Location's current URL when:
150
                    // 1. alias is history
151
                    // 2. alias is custom with forward flag true
152
                    // 3. requested URL is not case-sensitive equal with the one loaded
153
                    if ($urlAlias->isHistory === true || ($urlAlias->isCustom === true && $urlAlias->forward === true)) {
154
                        $request->attributes->set('semanticPathinfo', $this->generate($location));
155
                        $request->attributes->set('needsRedirect', true);
156
                        // Specify not to prepend siteaccess while redirecting when applicable since it would be already present (see UrlAliasGenerator::doGenerate())
157
                        $request->attributes->set('prependSiteaccessOnRedirect', false);
158
                    } elseif ($this->needsCaseRedirect($urlAlias, $requestedPath, $pathPrefix)) {
159
                        if ($urlAlias->destination instanceof Location) {
160
                            $request->attributes->set('locationId', $urlAlias->destination->id);
161
                        }
162
                        $request->attributes->set('semanticPathinfo', $this->removePathPrefix($urlAlias->path, $pathPrefix));
163
                        $request->attributes->set('needsRedirect', true);
164
                    }
165
166
                    if (isset($this->logger)) {
167
                        $this->logger->info("UrlAlias matched location #{$urlAlias->destination}. Forwarding to ViewController");
168
                    }
169
170
                    break;
171
172
                case URLAlias::RESOURCE:
173
                    // In URLAlias terms, "forward" means "redirect".
174
                    if ($urlAlias->forward) {
175
                        $request->attributes->set('semanticPathinfo', '/' . trim($urlAlias->destination, '/'));
176
                        $request->attributes->set('needsRedirect', true);
177
                    } elseif ($this->needsCaseRedirect($urlAlias, $requestedPath, $pathPrefix)) {
178
                        // Handle case-correction redirect
179
                        $request->attributes->set('semanticPathinfo', $this->removePathPrefix($urlAlias->path, $pathPrefix));
180
                        $request->attributes->set('needsRedirect', true);
181
                    } else {
182
                        $request->attributes->set('semanticPathinfo', '/' . trim($urlAlias->destination, '/'));
183
                        $request->attributes->set('needsForward', true);
184
                    }
185
186
                    break;
187
188
                case URLAlias::VIRTUAL:
189
                    // Handle case-correction redirect
190
                    if ($this->needsCaseRedirect($urlAlias, $requestedPath, $pathPrefix)) {
191
                        $request->attributes->set('semanticPathinfo', $this->removePathPrefix($urlAlias->path, $pathPrefix));
192
                        $request->attributes->set('needsRedirect', true);
193
                    } else {
194
                        // Virtual aliases should load the Content at homepage URL
195
                        $request->attributes->set('semanticPathinfo', '/');
196
                        $request->attributes->set('needsForward', true);
197
                    }
198
199
                    break;
200
            }
201
202
            return $params;
203
        } catch (NotFoundException $e) {
204
            throw new ResourceNotFoundException($e->getMessage(), $e->getCode(), $e);
205
        }
206
    }
207
208
    /**
209
     * Removes prefix from path.
210
     *
211
     * Checks for presence of $prefix and removes it from $path if found.
212
     *
213
     * @param string $path
214
     * @param string $prefix
215
     *
216
     * @return string
217
     */
218
    protected function removePathPrefix($path, $prefix)
219
    {
220
        if ($prefix !== '/' && mb_stripos($path, $prefix) === 0) {
221
            $path = mb_substr($path, mb_strlen($prefix));
222
        }
223
224
        return $path;
225
    }
226
227
    /**
228
     * Returns true of false on comparing $urlAlias->path and $path with case sensitivity.
229
     *
230
     * Used to determine if redirect is needed because requested path is case-different
231
     * from the stored one.
232
     *
233
     * @param \eZ\Publish\API\Repository\Values\Content\URLAlias $loadedUrlAlias
234
     * @param string $requestedPath
235
     * @param string $pathPrefix
236
     *
237
     * @return bool
238
     */
239
    protected function needsCaseRedirect(URLAlias $loadedUrlAlias, $requestedPath, $pathPrefix)
240
    {
241
        // If requested path is excluded from tree root jail, compare it to loaded UrlAlias directly.
242
        if ($this->generator->isUriPrefixExcluded($requestedPath)) {
243
            return strcmp($loadedUrlAlias->path, $requestedPath) !== 0;
244
        }
245
246
        // Compare loaded UrlAlias with requested path, prefixed with configured path prefix.
247
        return
248
            strcmp(
249
                $loadedUrlAlias->path,
250
                $pathPrefix . ($pathPrefix === '/' ? trim($requestedPath, '/') : rtrim($requestedPath, '/'))
251
            ) !== 0
252
        ;
253
    }
254
255
    /**
256
     * Returns the UrlAlias object to use, starting from the request.
257
     *
258
     * @param $pathinfo
259
     *
260
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the path does not exist or is not valid for the given language
261
     *
262
     * @return URLAlias
263
     */
264
    protected function getUrlAlias($pathinfo)
265
    {
266
        return $this->urlAliasService->lookup($pathinfo);
267
    }
268
269
    /**
270
     * Gets the RouteCollection instance associated with this Router.
271
     *
272
     * @return RouteCollection A RouteCollection instance
273
     */
274
    public function getRouteCollection()
275
    {
276
        return new RouteCollection();
277
    }
278
279
    /**
280
     * Generates a URL for a location, from the given parameters.
281
     *
282
     * It is possible to directly pass a Location object as the route name, as the ChainRouter allows it through ChainedRouterInterface.
283
     *
284
     * If $name is a route name, the "location" key in $parameters must be set to a valid eZ\Publish\API\Repository\Values\Content\Location object.
285
     * "locationId" can also be provided.
286
     *
287
     * If the generator is not able to generate the url, it must throw the RouteNotFoundException
288
     * as documented below.
289
     *
290
     * @see UrlAliasRouter::supports()
291
     *
292
     * @param string|\eZ\Publish\API\Repository\Values\Content\Location $name The name of the route or a Location instance
293
     * @param mixed $parameters An array of parameters
294
     * @param int $referenceType The type of reference to be generated (one of the constants)
295
     *
296
     * @throws \LogicException
297
     * @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
298
     * @throws \InvalidArgumentException
299
     *
300
     * @return string The generated URL
301
     *
302
     * @api
303
     */
304
    public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
305
    {
306
        // Direct access to Location
307
        if ($name instanceof Location) {
308
            return $this->generator->generate($name, $parameters, $referenceType);
309
        }
310
311
        // Normal route name
312
        if ($name === self::URL_ALIAS_ROUTE_NAME) {
313
            if (isset($parameters['location']) || isset($parameters['locationId'])) {
314
                // Check if location is a valid Location object
315
                if (isset($parameters['location']) && !$parameters['location'] instanceof Location) {
316
                    throw new LogicException(
317
                        "When generating an UrlAlias route, 'location' parameter must be a valid eZ\\Publish\\API\\Repository\\Values\\Content\\Location."
318
                    );
319
                }
320
321
                $location = isset($parameters['location']) ? $parameters['location'] : $this->locationService->loadLocation($parameters['locationId']);
322
                unset($parameters['location'], $parameters['locationId'], $parameters['viewType'], $parameters['layout']);
323
324
                return $this->generator->generate($location, $parameters, $referenceType);
325
            }
326
327
            if (isset($parameters['contentId'])) {
328
                $contentInfo = $this->contentService->loadContentInfo($parameters['contentId']);
329
                unset($parameters['contentId'], $parameters['viewType'], $parameters['layout']);
330
331
                if (empty($contentInfo->mainLocationId)) {
332
                    throw new LogicException('Cannot generate an UrlAlias route for content without main location.');
333
                }
334
335
                return $this->generator->generate(
336
                    $this->locationService->loadLocation($contentInfo->mainLocationId),
337
                    $parameters,
338
                    $referenceType
339
                );
340
            }
341
342
            throw new InvalidArgumentException(
343
                "When generating an UrlAlias route, either 'location', 'locationId' or 'contentId' must be provided."
344
            );
345
        }
346
347
        throw new RouteNotFoundException('Could not match route');
348
    }
349
350
    public function setContext(RequestContext $context)
351
    {
352
        $this->requestContext = $context;
353
        $this->generator->setRequestContext($context);
354
    }
355
356
    public function getContext()
357
    {
358
        return $this->requestContext;
359
    }
360
361
    /**
362
     * Not supported. Please use matchRequest() instead.
363
     *
364
     * @param $pathinfo
365
     *
366
     * @throws \RuntimeException
367
     */
368
    public function match($pathinfo)
369
    {
370
        throw new \RuntimeException("The UrlAliasRouter doesn't support the match() method. Please use matchRequest() instead.");
371
    }
372
373
    /**
374
     * Whether the router supports the thing in $name to generate a route.
375
     *
376
     * This check does not need to look if the specific instance can be
377
     * resolved to a route, only whether the router can generate routes from
378
     * objects of this class.
379
     *
380
     * @param mixed $name The route name or route object
381
     *
382
     * @return bool
383
     */
384
    public function supports($name)
385
    {
386
        return $name instanceof Location || $name === self::URL_ALIAS_ROUTE_NAME;
387
    }
388
389
    /**
390
     * @see Symfony\Cmf\Component\Routing\VersatileGeneratorInterface::getRouteDebugMessage()
391
     */
392
    public function getRouteDebugMessage($name, array $parameters = array())
393
    {
394
        if ($name instanceof RouteObjectInterface) {
395
            return 'Route with key ' . $name->getRouteKey();
396
        }
397
398
        if ($name instanceof SymfonyRoute) {
399
            return 'Route with pattern ' . $name->getPath();
400
        }
401
402
        return $name;
403
    }
404
}
405