1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 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: 23: 24: 25: 26: 27:
28: class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface
29: {
30: 31: 32:
33: protected $routes;
34:
35: 36: 37:
38: protected $context;
39:
40: 41: 42:
43: protected $strictRequirements = true;
44:
45: 46: 47:
48: protected $logger;
49:
50: 51: 52: 53: 54: 55: 56: 57:
58: protected $decodedChars = array(
59:
60:
61:
62: '%2F' => '/',
63:
64:
65: '%40' => '@',
66: '%3A' => ':',
67:
68:
69: '%3B' => ';',
70: '%2C' => ',',
71: '%3D' => '=',
72: '%2B' => '+',
73: '%21' => '!',
74: '%2A' => '*',
75: '%7C' => '|',
76: );
77:
78: 79: 80: 81: 82: 83: 84:
85: public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null)
86: {
87: $this->routes = $routes;
88: $this->context = $context;
89: $this->logger = $logger;
90: }
91:
92: 93: 94:
95: public function setContext(RequestContext $context)
96: {
97: $this->context = $context;
98: }
99:
100: 101: 102:
103: public function getContext()
104: {
105: return $this->context;
106: }
107:
108: 109: 110:
111: public function setStrictRequirements($enabled)
112: {
113: $this->strictRequirements = null === $enabled ? null : (bool) $enabled;
114: }
115:
116: 117: 118:
119: public function isStrictRequirements()
120: {
121: return $this->strictRequirements;
122: }
123:
124: 125: 126:
127: public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
128: {
129: if (null === $route = $this->routes->get($name)) {
130: throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
131: }
132:
133:
134: $compiledRoute = $route->compile();
135:
136: return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes());
137: }
138:
139: 140: 141: 142: 143:
144: protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array())
145: {
146: if (is_bool($referenceType) || is_string($referenceType)) {
147: @trigger_error('The hardcoded value you are using for the $referenceType argument of the '.__CLASS__.'::generate method is deprecated since version 2.8 and will not be supported anymore in 3.0. Use the constants defined in the UrlGeneratorInterface instead.', E_USER_DEPRECATED);
148:
149: if (true === $referenceType) {
150: $referenceType = self::ABSOLUTE_URL;
151: } elseif (false === $referenceType) {
152: $referenceType = self::ABSOLUTE_PATH;
153: } elseif ('relative' === $referenceType) {
154: $referenceType = self::RELATIVE_PATH;
155: } elseif ('network' === $referenceType) {
156: $referenceType = self::NETWORK_PATH;
157: }
158: }
159:
160: $variables = array_flip($variables);
161: $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters);
162:
163:
164: if ($diff = array_diff_key($variables, $mergedParams)) {
165: throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name));
166: }
167:
168: $url = '';
169: $optional = true;
170: foreach ($tokens as $token) {
171: if ('variable' === $token[0]) {
172: if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) {
173:
174: if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) {
175: $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]]);
176: if ($this->strictRequirements) {
177: throw new InvalidParameterException($message);
178: }
179:
180: if ($this->logger) {
181: $this->logger->error($message);
182: }
183:
184: return;
185: }
186:
187: $url = $token[1].$mergedParams[$token[3]].$url;
188: $optional = false;
189: }
190: } else {
191:
192: $url = $token[1].$url;
193: $optional = false;
194: }
195: }
196:
197: if ('' === $url) {
198: $url = '/';
199: }
200:
201:
202: $url = strtr(rawurlencode($url), $this->decodedChars);
203:
204:
205:
206:
207: $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/'));
208: if ('/..' === substr($url, -3)) {
209: $url = substr($url, 0, -2).'%2E%2E';
210: } elseif ('/.' === substr($url, -2)) {
211: $url = substr($url, 0, -1).'%2E';
212: }
213:
214: $schemeAuthority = '';
215: if ($host = $this->context->getHost()) {
216: $scheme = $this->context->getScheme();
217:
218: if ($requiredSchemes) {
219: if (!in_array($scheme, $requiredSchemes, true)) {
220: $referenceType = self::ABSOLUTE_URL;
221: $scheme = current($requiredSchemes);
222: }
223: } elseif (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme !== $req) {
224:
225: $referenceType = self::ABSOLUTE_URL;
226: $scheme = $req;
227: }
228:
229: if ($hostTokens) {
230: $routeHost = '';
231: foreach ($hostTokens as $token) {
232: if ('variable' === $token[0]) {
233: if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i', $mergedParams[$token[3]])) {
234: $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]]);
235:
236: if ($this->strictRequirements) {
237: throw new InvalidParameterException($message);
238: }
239:
240: if ($this->logger) {
241: $this->logger->error($message);
242: }
243:
244: return;
245: }
246:
247: $routeHost = $token[1].$mergedParams[$token[3]].$routeHost;
248: } else {
249: $routeHost = $token[1].$routeHost;
250: }
251: }
252:
253: if ($routeHost !== $host) {
254: $host = $routeHost;
255: if (self::ABSOLUTE_URL !== $referenceType) {
256: $referenceType = self::NETWORK_PATH;
257: }
258: }
259: }
260:
261: if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) {
262: $port = '';
263: if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
264: $port = ':'.$this->context->getHttpPort();
265: } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) {
266: $port = ':'.$this->context->getHttpsPort();
267: }
268:
269: $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://";
270: $schemeAuthority .= $host.$port;
271: }
272: }
273:
274: if (self::RELATIVE_PATH === $referenceType) {
275: $url = self::getRelativePath($this->context->getPathInfo(), $url);
276: } else {
277: $url = $schemeAuthority.$this->context->getBaseUrl().$url;
278: }
279:
280:
281: $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, function ($a, $b) {
282: return $a == $b ? 0 : 1;
283: });
284:
285: if ($extra && $query = http_build_query($extra, '', '&')) {
286:
287:
288: $url .= '?'.strtr($query, array('%2F' => '/'));
289: }
290:
291: return $url;
292: }
293:
294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313:
314: public static function getRelativePath($basePath, $targetPath)
315: {
316: if ($basePath === $targetPath) {
317: return '';
318: }
319:
320: $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
321: $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath);
322: array_pop($sourceDirs);
323: $targetFile = array_pop($targetDirs);
324:
325: foreach ($sourceDirs as $i => $dir) {
326: if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
327: unset($sourceDirs[$i], $targetDirs[$i]);
328: } else {
329: break;
330: }
331: }
332:
333: $targetDirs[] = $targetFile;
334: $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
335:
336:
337:
338:
339:
340: return '' === $path || '/' === $path[0]
341: || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
342: ? "./$path" : $path;
343: }
344: }
345: