Completed
Push — EZP-31644 ( 2e0a1e...93bb44 )
by
unknown
19:12
created

UrlAliasGenerator   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 0
loc 206
rs 10
c 0
b 0
f 0
wmc 24
lcom 1
cbo 9

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A setRootLocationId() 0 4 1
A setExcludedUriPrefixes() 0 4 1
A getPathPrefixByRootLocationId() 0 24 4
A isUriPrefixExcluded() 0 11 3
A loadLocation() 0 9 1
C doGenerate() 0 54 13
1
<?php
2
3
/**
4
 * File containing the UrlAliasGenerator 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\Generator;
10
11
use eZ\Publish\API\Repository\Repository;
12
use eZ\Publish\Core\MVC\ConfigResolverInterface;
13
use eZ\Publish\Core\MVC\Symfony\Routing\Generator;
14
use Symfony\Component\Routing\RouterInterface;
15
16
/**
17
 * URL generator for UrlAlias based links.
18
 *
19
 * @see \eZ\Publish\Core\MVC\Symfony\Routing\UrlAliasRouter
20
 */
21
class UrlAliasGenerator extends Generator
22
{
23
    const INTERNAL_LOCATION_ROUTE = '_ezpublishLocation';
24
    const INTERNAL_CONTENT_VIEW_ROUTE = '_ez_content_view';
25
26
    /**
27
     * @var \eZ\Publish\Core\Repository\Repository
28
     */
29
    private $repository;
30
31
    /**
32
     * The default router (that works with declared routes).
33
     *
34
     * @var \Symfony\Component\Routing\RouterInterface
35
     */
36
    private $defaultRouter;
37
38
    /**
39
     * @var int
40
     */
41
    private $rootLocationId;
42
43
    /**
44
     * @var array
45
     */
46
    private $excludedUriPrefixes = [];
47
48
    /**
49
     * @var array
50
     */
51
    private $pathPrefixMap = [];
52
53
    /**
54
     * @var \eZ\Publish\Core\MVC\ConfigResolverInterface
55
     */
56
    private $configResolver;
57
58
    /**
59
     * Array of characters that are potentially unsafe for output for (x)html, json, etc,
60
     * and respective url-encoded value.
61
     *
62
     * @var array
63
     */
64
    private $unsafeCharMap;
65
66
    public function __construct(Repository $repository, RouterInterface $defaultRouter, ConfigResolverInterface $configResolver, array $unsafeCharMap = [])
67
    {
68
        $this->repository = $repository;
0 ignored issues
show
Documentation Bug introduced by
$repository is of type object<eZ\Publish\API\Repository\Repository>, but the property $repository was declared to be of type object<eZ\Publish\Core\Repository\Repository>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
69
        $this->defaultRouter = $defaultRouter;
70
        $this->configResolver = $configResolver;
71
        $this->unsafeCharMap = $unsafeCharMap;
72
    }
73
74
    /**
75
     * Generates the URL from $urlResource and $parameters.
76
     * Entries in $parameters will be added in the query string.
77
     *
78
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
79
     * @param array $parameters
80
     *
81
     * @return string
82
     */
83
    public function doGenerate($location, array $parameters)
84
    {
85
        $urlAliasService = $this->repository->getURLAliasService();
86
87
        $siteaccess = null;
88
        if (isset($parameters['siteaccess'])) {
89
            $siteaccess = $parameters['siteaccess'];
90
            unset($parameters['siteaccess']);
91
        }
92
93
        if ($siteaccess) {
94
            // We generate for a different SiteAccess, so potentially in a different language.
95
            $languages = $this->configResolver->getParameter('languages', null, $siteaccess);
96
            $urlAliases = $urlAliasService->listLocationAliases($location, false, null, null, $languages);
97
            // Use the target SiteAccess root location
98
            $rootLocationId = $this->configResolver->getParameter('content.tree_root.location_id', null, $siteaccess);
99
        } else {
100
            $languages = null;
101
            $urlAliases = $urlAliasService->listLocationAliases($location, false);
102
            $rootLocationId = $this->rootLocationId;
103
        }
104
105
        $queryString = '';
106
        unset($parameters['language'], $parameters['contentId']);
107
        if (!empty($parameters)) {
108
            $queryString = '?' . http_build_query($parameters, '', '&');
109
        }
110
111
        if (!empty($urlAliases)) {
112
            $path = $urlAliases[0]->path;
113
            // Remove rootLocation's prefix if needed.
114
            if ($rootLocationId !== null) {
115
                $pathPrefix = $this->getPathPrefixByRootLocationId($rootLocationId, $languages, $siteaccess);
116
                // "/" cannot be considered as a path prefix since it's root, so we ignore it.
117
                if ($pathPrefix !== '/' && ($path === $pathPrefix || mb_stripos($path, $pathPrefix . '/') === 0)) {
118
                    $path = mb_substr($path, mb_strlen($pathPrefix));
119
                } elseif ($pathPrefix !== '/' && !$this->isUriPrefixExcluded($path) && $this->logger !== null) {
120
                    // Location path is outside configured content tree and doesn't have an excluded prefix.
121
                    // This is most likely an error (from content edition or link generation logic).
122
                    $this->logger->warning("Generating a link to a location outside root content tree: '$path' is outside tree starting to location #$rootLocationId");
123
                }
124
            }
125
        } else {
126
            $path = $this->defaultRouter->generate(
127
                self::INTERNAL_CONTENT_VIEW_ROUTE,
128
                ['contentId' => $location->contentId, 'locationId' => $location->id]
129
            );
130
        }
131
132
        $path = $path ?: '/';
133
134
        // replace potentially unsafe characters with url-encoded counterpart
135
        return strtr($path . $queryString, $this->unsafeCharMap);
136
    }
137
138
    /**
139
     * Injects current root locationId that will be used for link generation.
140
     *
141
     * @param int $rootLocationId
142
     */
143
    public function setRootLocationId($rootLocationId)
144
    {
145
        $this->rootLocationId = $rootLocationId;
146
    }
147
148
    /**
149
     * @param array $excludedUriPrefixes
150
     */
151
    public function setExcludedUriPrefixes(array $excludedUriPrefixes)
152
    {
153
        $this->excludedUriPrefixes = $excludedUriPrefixes;
154
    }
155
156
    /**
157
     * Returns path corresponding to $rootLocationId.
158
     *
159
     * @param int $rootLocationId
160
     * @param array $languages
161
     * @param string $siteaccess
162
     *
163
     * @return string
164
     */
165
    public function getPathPrefixByRootLocationId($rootLocationId, $languages = null, $siteaccess = null)
166
    {
167
        if (!$rootLocationId) {
168
            return '';
169
        }
170
171
        if (!isset($this->pathPrefixMap[$siteaccess])) {
172
            $this->pathPrefixMap[$siteaccess] = [];
173
        }
174
175
        if (!isset($this->pathPrefixMap[$siteaccess][$rootLocationId])) {
176
            $this->pathPrefixMap[$siteaccess][$rootLocationId] = $this->repository
177
                ->getURLAliasService()
178
                ->reverseLookup(
179
                    $this->loadLocation($rootLocationId),
180
                    null,
181
                    false,
182
                    $languages
183
                )
184
                ->path;
185
        }
186
187
        return $this->pathPrefixMap[$siteaccess][$rootLocationId];
188
    }
189
190
    /**
191
     * Checks if passed URI has an excluded prefix, when a root location is defined.
192
     *
193
     * @param string $uri
194
     *
195
     * @return bool
196
     */
197
    public function isUriPrefixExcluded($uri)
198
    {
199
        foreach ($this->excludedUriPrefixes as $excludedPrefix) {
200
            $excludedPrefix = '/' . trim($excludedPrefix, '/');
201
            if (mb_stripos($uri, $excludedPrefix) === 0) {
202
                return true;
203
            }
204
        }
205
206
        return false;
207
    }
208
209
    /**
210
     * Loads a location by its locationId, regardless to user limitations since the router is invoked BEFORE security (no user authenticated yet).
211
     * Not to be used for link generation.
212
     *
213
     * @param int $locationId
214
     *
215
     * @return \eZ\Publish\Core\Repository\Values\Content\Location
216
     */
217
    public function loadLocation($locationId)
218
    {
219
        return $this->repository->sudo(
220
            function (Repository $repository) use ($locationId) {
221
                /* @var $repository \eZ\Publish\Core\Repository\Repository */
222
                return $repository->getLocationService()->loadLocation($locationId);
223
            }
224
        );
225
    }
226
}
227