@@ -127,7 +127,7 @@ public function isStrictRequirements()
127127 /**
128128 * {@inheritDoc}
129129 */
130- public function generate ($ name , $ parameters = array (), $ absolute = false )
130+ public function generate ($ name , $ parameters = array (), $ referenceType = self :: ABSOLUTE_PATH )
131131 {
132132 if (null === $ route = $ this ->routes ->get ($ name )) {
133133 throw new RouteNotFoundException (sprintf ('Route "%s" does not exist. ' , $ name ));
@@ -136,15 +136,40 @@ public function generate($name, $parameters = array(), $absolute = false)
136136 // the Route has a cache of its own and is not recompiled as long as it does not get modified
137137 $ compiledRoute = $ route ->compile ();
138138
139- return $ this ->doGenerate ($ compiledRoute ->getVariables (), $ route ->getDefaults (), $ route ->getRequirements (), $ compiledRoute ->getTokens (), $ parameters , $ name , $ absolute , $ compiledRoute ->getHostnameTokens ());
139+ return $ this ->doGenerate ($ compiledRoute ->getVariables (), $ route ->getDefaults (), $ route ->getRequirements (), $ compiledRoute ->getTokens (), $ parameters , $ name , $ referenceType , $ compiledRoute ->getHostnameTokens ());
140+ }
141+
142+ /**
143+ * This method converts the reference type to the new value introduced in Symfony 2.2. It can be used by
144+ * other UrlGenerator implementations to be BC with Symfony 2.1. Reference type was a Boolean called
145+ * $absolute in Symfony 2.1 and only supported two reference types.
146+ *
147+ * @param Boolean $absolute Whether to generate an absolute URL
148+ *
149+ * @return string The new reference type
150+ *
151+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
152+ */
153+ public static function convertReferenceType ($ absolute )
154+ {
155+ if (false === $ absolute ) {
156+ return self ::ABSOLUTE_PATH ;
157+ }
158+ if (true === $ absolute ) {
159+ return self ::ABSOLUTE_URL ;
160+ }
161+
162+ return $ absolute ;
140163 }
141164
142165 /**
143166 * @throws MissingMandatoryParametersException When route has some missing mandatory parameters
144167 * @throws InvalidParameterException When a parameter value is not correct
145168 */
146- protected function doGenerate ($ variables , $ defaults , $ requirements , $ tokens , $ parameters , $ name , $ absolute , $ hostnameTokens )
169+ protected function doGenerate ($ variables , $ defaults , $ requirements , $ tokens , $ parameters , $ name , $ referenceType , $ hostnameTokens )
147170 {
171+ $ referenceType = self ::convertReferenceType ($ referenceType );
172+
148173 $ variables = array_flip ($ variables );
149174 $ mergedParams = array_replace ($ defaults , $ this ->context ->getParameters (), $ parameters );
150175
@@ -186,8 +211,8 @@ protected function doGenerate($variables, $defaults, $requirements, $tokens, $pa
186211 $ url = '/ ' ;
187212 }
188213
189- // do not encode the contexts base url as it is already encoded (see Symfony\Component\HttpFoundation\Request)
190- $ url = $ this -> context -> getBaseUrl (). strtr (rawurlencode ($ url ), $ this ->decodedChars );
214+ // the contexts base url is already encoded (see Symfony\Component\HttpFoundation\Request)
215+ $ url = strtr (rawurlencode ($ url ), $ this ->decodedChars );
191216
192217 // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3
193218 // so we need to encode them as they are not used for this purpose here
@@ -199,16 +224,11 @@ protected function doGenerate($variables, $defaults, $requirements, $tokens, $pa
199224 $ url = substr ($ url , 0 , -1 ) . '%2E ' ;
200225 }
201226
202- // add a query string if needed
203- $ extra = array_diff_key ($ parameters , $ variables );
204- if ($ extra && $ query = http_build_query ($ extra , '' , '& ' )) {
205- $ url .= '? ' .$ query ;
206- }
207-
227+ $ schemeAuthority = '' ;
208228 if ($ host = $ this ->context ->getHost ()) {
209229 $ scheme = $ this ->context ->getScheme ();
210- if (isset ($ requirements ['_scheme ' ]) && ($ req = strtolower ($ requirements ['_scheme ' ])) && $ scheme != $ req ) {
211- $ absolute = true ;
230+ if (isset ($ requirements ['_scheme ' ]) && ($ req = strtolower ($ requirements ['_scheme ' ])) && $ scheme !== $ req ) {
231+ $ referenceType = self :: ABSOLUTE_URL ;
212232 $ scheme = $ req ;
213233 }
214234
@@ -231,29 +251,95 @@ protected function doGenerate($variables, $defaults, $requirements, $tokens, $pa
231251 }
232252
233253 $ routeHost = $ token [1 ].$ mergedParams [$ token [3 ]].$ routeHost ;
234- } elseif ( ' text ' === $ token [ 0 ]) {
254+ } else {
235255 $ routeHost = $ token [1 ].$ routeHost ;
236256 }
237257 }
238258
239- if ($ routeHost != $ host ) {
259+ if ($ routeHost !== $ host ) {
240260 $ host = $ routeHost ;
241- $ absolute = true ;
261+ if (self ::ABSOLUTE_URL !== $ referenceType ) {
262+ $ referenceType = self ::NETWORK_PATH ;
263+ }
242264 }
243265 }
244266
245- if ($ absolute ) {
267+ if (self :: ABSOLUTE_URL === $ referenceType || self :: NETWORK_PATH === $ referenceType ) {
246268 $ port = '' ;
247269 if ('http ' === $ scheme && 80 != $ this ->context ->getHttpPort ()) {
248270 $ port = ': ' .$ this ->context ->getHttpPort ();
249271 } elseif ('https ' === $ scheme && 443 != $ this ->context ->getHttpsPort ()) {
250272 $ port = ': ' .$ this ->context ->getHttpsPort ();
251273 }
252274
253- $ url = $ scheme .':// ' .$ host .$ port .$ url ;
275+ $ schemeAuthority = self ::NETWORK_PATH === $ referenceType ? '// ' : "$ scheme:// " ;
276+ $ schemeAuthority .= $ host .$ port ;
254277 }
255278 }
256279
280+ if (self ::RELATIVE_PATH === $ referenceType ) {
281+ $ url = self ::getRelativePath ($ this ->context ->getPathInfo (), $ url );
282+ } else {
283+ $ url = $ schemeAuthority .$ this ->context ->getBaseUrl ().$ url ;
284+ }
285+
286+ // add a query string if needed
287+ $ extra = array_diff_key ($ parameters , $ variables );
288+ if ($ extra && $ query = http_build_query ($ extra , '' , '& ' )) {
289+ $ url .= '? ' .$ query ;
290+ }
291+
257292 return $ url ;
258293 }
294+
295+ /**
296+ * Returns the target path as relative reference from the base path.
297+ *
298+ * Only the URIs path component (no schema, hostname etc.) is relevant and must be given, starting with a slash.
299+ * Both paths must be absolute and not contain relative parts.
300+ * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
301+ * Furthermore, they can be used to reduce the link size in documents.
302+ *
303+ * Example target paths, given a base path of "/a/b/c/d":
304+ * - "/a/b/c/d" -> ""
305+ * - "/a/b/c/" -> "./"
306+ * - "/a/b/" -> "../"
307+ * - "/a/b/c/other" -> "other"
308+ * - "/a/x/y" -> "../../x/y"
309+ *
310+ * @param string $basePath The base path
311+ * @param string $targetPath The target path
312+ *
313+ * @return string The relative target path
314+ */
315+ public static function getRelativePath ($ basePath , $ targetPath )
316+ {
317+ if ($ basePath === $ targetPath ) {
318+ return '' ;
319+ }
320+
321+ $ sourceDirs = explode ('/ ' , isset ($ basePath [0 ]) && '/ ' === $ basePath [0 ] ? substr ($ basePath , 1 ) : $ basePath );
322+ $ targetDirs = explode ('/ ' , isset ($ targetPath [0 ]) && '/ ' === $ targetPath [0 ] ? substr ($ targetPath , 1 ) : $ targetPath );
323+ array_pop ($ sourceDirs );
324+ $ targetFile = array_pop ($ targetDirs );
325+
326+ foreach ($ sourceDirs as $ i => $ dir ) {
327+ if (isset ($ targetDirs [$ i ]) && $ dir === $ targetDirs [$ i ]) {
328+ unset($ sourceDirs [$ i ], $ targetDirs [$ i ]);
329+ } else {
330+ break ;
331+ }
332+ }
333+
334+ $ targetDirs [] = $ targetFile ;
335+ $ path = str_repeat ('../ ' , count ($ sourceDirs )) . implode ('/ ' , $ targetDirs );
336+
337+ // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
338+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
339+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
340+ // (see http://tools.ietf.org/html/rfc3986#section-4.2).
341+ return '' === $ path || '/ ' === $ path [0 ]
342+ || false !== ($ colonPos = strpos ($ path , ': ' )) && ($ colonPos < ($ slashPos = strpos ($ path , '/ ' )) || false === $ slashPos )
343+ ? "./ $ path " : $ path ;
344+ }
259345}
0 commit comments