diff options
Diffstat (limited to 'vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php')
-rw-r--r-- | vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php b/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php new file mode 100644 index 00000000..f224cb3f --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php | |||
@@ -0,0 +1,322 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of the Symfony package. | ||
5 | * | ||
6 | * (c) Fabien Potencier <fabien@symfony.com> | ||
7 | * | ||
8 | * For the full copyright and license information, please view the LICENSE | ||
9 | * file that was distributed with this source code. | ||
10 | */ | ||
11 | |||
12 | namespace Symfony\Component\Routing\Generator; | ||
13 | |||
14 | use Symfony\Component\Routing\RouteCollection; | ||
15 | use Symfony\Component\Routing\RequestContext; | ||
16 | use Symfony\Component\Routing\Exception\InvalidParameterException; | ||
17 | use Symfony\Component\Routing\Exception\RouteNotFoundException; | ||
18 | use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; | ||
19 | use Psr\Log\LoggerInterface; | ||
20 | |||
21 | /** | ||
22 | * UrlGenerator can generate a URL or a path for any route in the RouteCollection | ||
23 | * based on the passed parameters. | ||
24 | * | ||
25 | * @author Fabien Potencier <fabien@symfony.com> | ||
26 | * @author Tobias Schultze <http://tobion.de> | ||
27 | * | ||
28 | * @api | ||
29 | */ | ||
30 | class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface | ||
31 | { | ||
32 | /** | ||
33 | * @var RouteCollection | ||
34 | */ | ||
35 | protected $routes; | ||
36 | |||
37 | /** | ||
38 | * @var RequestContext | ||
39 | */ | ||
40 | protected $context; | ||
41 | |||
42 | /** | ||
43 | * @var Boolean|null | ||
44 | */ | ||
45 | protected $strictRequirements = true; | ||
46 | |||
47 | /** | ||
48 | * @var LoggerInterface|null | ||
49 | */ | ||
50 | protected $logger; | ||
51 | |||
52 | /** | ||
53 | * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. | ||
54 | * | ||
55 | * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars | ||
56 | * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. | ||
57 | * "?" and "#" (would be interpreted wrongly as query and fragment identifier), | ||
58 | * "'" and """ (are used as delimiters in HTML). | ||
59 | */ | ||
60 | protected $decodedChars = array( | ||
61 | // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning | ||
62 | // some webservers don't allow the slash in encoded form in the path for security reasons anyway | ||
63 | // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss | ||
64 | '%2F' => '/', | ||
65 | // the following chars are general delimiters in the URI specification but have only special meaning in the authority component | ||
66 | // so they can safely be used in the path in unencoded form | ||
67 | '%40' => '@', | ||
68 | '%3A' => ':', | ||
69 | // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally | ||
70 | // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability | ||
71 | '%3B' => ';', | ||
72 | '%2C' => ',', | ||
73 | '%3D' => '=', | ||
74 | '%2B' => '+', | ||
75 | '%21' => '!', | ||
76 | '%2A' => '*', | ||
77 | '%7C' => '|', | ||
78 | ); | ||
79 | |||
80 | /** | ||
81 | * Constructor. | ||
82 | * | ||
83 | * @param RouteCollection $routes A RouteCollection instance | ||
84 | * @param RequestContext $context The context | ||
85 | * @param LoggerInterface|null $logger A logger instance | ||
86 | * | ||
87 | * @api | ||
88 | */ | ||
89 | public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) | ||
90 | { | ||
91 | $this->routes = $routes; | ||
92 | $this->context = $context; | ||
93 | $this->logger = $logger; | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * {@inheritdoc} | ||
98 | */ | ||
99 | public function setContext(RequestContext $context) | ||
100 | { | ||
101 | $this->context = $context; | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * {@inheritdoc} | ||
106 | */ | ||
107 | public function getContext() | ||
108 | { | ||
109 | return $this->context; | ||
110 | } | ||
111 | |||
112 | /** | ||
113 | * {@inheritdoc} | ||
114 | */ | ||
115 | public function setStrictRequirements($enabled) | ||
116 | { | ||
117 | $this->strictRequirements = null === $enabled ? null : (Boolean) $enabled; | ||
118 | } | ||
119 | |||
120 | /** | ||
121 | * {@inheritdoc} | ||
122 | */ | ||
123 | public function isStrictRequirements() | ||
124 | { | ||
125 | return $this->strictRequirements; | ||
126 | } | ||
127 | |||
128 | /** | ||
129 | * {@inheritDoc} | ||
130 | */ | ||
131 | public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) | ||
132 | { | ||
133 | if (null === $route = $this->routes->get($name)) { | ||
134 | throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); | ||
135 | } | ||
136 | |||
137 | // the Route has a cache of its own and is not recompiled as long as it does not get modified | ||
138 | $compiledRoute = $route->compile(); | ||
139 | |||
140 | return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens()); | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route | ||
145 | * @throws InvalidParameterException When a parameter value for a placeholder is not correct because | ||
146 | * it does not match the requirement | ||
147 | */ | ||
148 | protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens) | ||
149 | { | ||
150 | $variables = array_flip($variables); | ||
151 | $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); | ||
152 | |||
153 | // all params must be given | ||
154 | if ($diff = array_diff_key($variables, $mergedParams)) { | ||
155 | throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name)); | ||
156 | } | ||
157 | |||
158 | $url = ''; | ||
159 | $optional = true; | ||
160 | foreach ($tokens as $token) { | ||
161 | if ('variable' === $token[0]) { | ||
162 | if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { | ||
163 | // check requirement | ||
164 | if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { | ||
165 | $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); | ||
166 | if ($this->strictRequirements) { | ||
167 | throw new InvalidParameterException($message); | ||
168 | } | ||
169 | |||
170 | if ($this->logger) { | ||
171 | $this->logger->error($message); | ||
172 | } | ||
173 | |||
174 | return null; | ||
175 | } | ||
176 | |||
177 | $url = $token[1].$mergedParams[$token[3]].$url; | ||
178 | $optional = false; | ||
179 | } | ||
180 | } else { | ||
181 | // static text | ||
182 | $url = $token[1].$url; | ||
183 | $optional = false; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | if ('' === $url) { | ||
188 | $url = '/'; | ||
189 | } | ||
190 | |||
191 | // the contexts base url is already encoded (see Symfony\Component\HttpFoundation\Request) | ||
192 | $url = strtr(rawurlencode($url), $this->decodedChars); | ||
193 | |||
194 | // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 | ||
195 | // so we need to encode them as they are not used for this purpose here | ||
196 | // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route | ||
197 | $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/')); | ||
198 | if ('/..' === substr($url, -3)) { | ||
199 | $url = substr($url, 0, -2).'%2E%2E'; | ||
200 | } elseif ('/.' === substr($url, -2)) { | ||
201 | $url = substr($url, 0, -1).'%2E'; | ||
202 | } | ||
203 | |||
204 | $schemeAuthority = ''; | ||
205 | if ($host = $this->context->getHost()) { | ||
206 | $scheme = $this->context->getScheme(); | ||
207 | if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme !== $req) { | ||
208 | $referenceType = self::ABSOLUTE_URL; | ||
209 | $scheme = $req; | ||
210 | } | ||
211 | |||
212 | if ($hostTokens) { | ||
213 | $routeHost = ''; | ||
214 | foreach ($hostTokens as $token) { | ||
215 | if ('variable' === $token[0]) { | ||
216 | if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { | ||
217 | $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); | ||
218 | |||
219 | if ($this->strictRequirements) { | ||
220 | throw new InvalidParameterException($message); | ||
221 | } | ||
222 | |||
223 | if ($this->logger) { | ||
224 | $this->logger->error($message); | ||
225 | } | ||
226 | |||
227 | return null; | ||
228 | } | ||
229 | |||
230 | $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; | ||
231 | } else { | ||
232 | $routeHost = $token[1].$routeHost; | ||
233 | } | ||
234 | } | ||
235 | |||
236 | if ($routeHost !== $host) { | ||
237 | $host = $routeHost; | ||
238 | if (self::ABSOLUTE_URL !== $referenceType) { | ||
239 | $referenceType = self::NETWORK_PATH; | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | |||
244 | if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { | ||
245 | $port = ''; | ||
246 | if ('http' === $scheme && 80 != $this->context->getHttpPort()) { | ||
247 | $port = ':'.$this->context->getHttpPort(); | ||
248 | } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { | ||
249 | $port = ':'.$this->context->getHttpsPort(); | ||
250 | } | ||
251 | |||
252 | $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; | ||
253 | $schemeAuthority .= $host.$port; | ||
254 | } | ||
255 | } | ||
256 | |||
257 | if (self::RELATIVE_PATH === $referenceType) { | ||
258 | $url = self::getRelativePath($this->context->getPathInfo(), $url); | ||
259 | } else { | ||
260 | $url = $schemeAuthority.$this->context->getBaseUrl().$url; | ||
261 | } | ||
262 | |||
263 | // add a query string if needed | ||
264 | $extra = array_diff_key($parameters, $variables, $defaults); | ||
265 | if ($extra && $query = http_build_query($extra, '', '&')) { | ||
266 | $url .= '?'.$query; | ||
267 | } | ||
268 | |||
269 | return $url; | ||
270 | } | ||
271 | |||
272 | /** | ||
273 | * Returns the target path as relative reference from the base path. | ||
274 | * | ||
275 | * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. | ||
276 | * Both paths must be absolute and not contain relative parts. | ||
277 | * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. | ||
278 | * Furthermore, they can be used to reduce the link size in documents. | ||
279 | * | ||
280 | * Example target paths, given a base path of "/a/b/c/d": | ||
281 | * - "/a/b/c/d" -> "" | ||
282 | * - "/a/b/c/" -> "./" | ||
283 | * - "/a/b/" -> "../" | ||
284 | * - "/a/b/c/other" -> "other" | ||
285 | * - "/a/x/y" -> "../../x/y" | ||
286 | * | ||
287 | * @param string $basePath The base path | ||
288 | * @param string $targetPath The target path | ||
289 | * | ||
290 | * @return string The relative target path | ||
291 | */ | ||
292 | public static function getRelativePath($basePath, $targetPath) | ||
293 | { | ||
294 | if ($basePath === $targetPath) { | ||
295 | return ''; | ||
296 | } | ||
297 | |||
298 | $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); | ||
299 | $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); | ||
300 | array_pop($sourceDirs); | ||
301 | $targetFile = array_pop($targetDirs); | ||
302 | |||
303 | foreach ($sourceDirs as $i => $dir) { | ||
304 | if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { | ||
305 | unset($sourceDirs[$i], $targetDirs[$i]); | ||
306 | } else { | ||
307 | break; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | $targetDirs[] = $targetFile; | ||
312 | $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs); | ||
313 | |||
314 | // A reference to the same base directory or an empty subdirectory must be prefixed with "./". | ||
315 | // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used | ||
316 | // as the first segment of a relative-path reference, as it would be mistaken for a scheme name | ||
317 | // (see http://tools.ietf.org/html/rfc3986#section-4.2). | ||
318 | return '' === $path || '/' === $path[0] | ||
319 | || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) | ||
320 | ? "./$path" : $path; | ||
321 | } | ||
322 | } | ||