diff --git a/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php b/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php index 5c91e6251cd09000c9c2cc489aea9c3da9c1292c..79ed3738d9251a6acde9ad93d3ca67cefa38ebe3 100644 --- a/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php +++ b/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php @@ -29,6 +29,20 @@ interface PathBasedGeneratorInterface { */ public function generateFromPath($path = NULL, $options = array()); + /** + * Gets the internal path of a route. + * + * @param string $name + * The route name. + * @param array $parameters + * An array of parameters as passed to + * \Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(). + * + * @return string + * The internal Drupal path corresponding to the route. + */ + public function getPathFromRoute($name, $parameters = array()); + /** * Sets the $request property. * diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php index 972058ab771ae0cdc8e929dbe8495c07eb109ac9..402caa63cec8035aa81a9f476c62016160a7c2cc 100644 --- a/core/lib/Drupal/Core/Routing/UrlGenerator.php +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php @@ -105,17 +105,31 @@ public function setRequest(Request $request) { } /** - * Implements Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(). + * {@inheritdoc} */ - public function generate($name, $parameters = array(), $absolute = FALSE) { - - if ($name instanceof SymfonyRoute) { - $route = $name; - } - elseif (NULL === $route = $this->provider->getRouteByName($name, $parameters)) { - throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name)); - } + public function getPathFromRoute($name, $parameters = array()) { + $route = $this->getRoute($name, $parameters); + $path = $this->getInternalPathFromRoute($route, $parameters); + // Router-based paths may have a querystring on them but Drupal paths may + // not have one, so remove any ? and anything after it. For generate() this + // is handled in processPath(). + $path = preg_replace('/\?.*/', '', $path); + return trim($path, '/'); + } + /** + * Gets the path of a route. + * + * @param \Symfony\Component\Routing\Route $route + * The route object. + * @param array $parameters + * An array of parameters as passed to + * \Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(). + * + * @return string + * The url path corresponding to the route, without the base path. + */ + protected function getInternalPathFromRoute(SymfonyRoute $route, $parameters = array()) { // The Route has a cache of its own and is not recompiled as long as it does // not get modified. $compiledRoute = $route->compile(); @@ -125,10 +139,9 @@ public function generate($name, $parameters = array(), $absolute = FALSE) { // We need to bypass the doGenerate() method's handling of absolute URLs as // we handle that ourselves after processing the path. if (isset($route_requirements['_scheme'])) { - $scheme_req = $route_requirements['_scheme']; unset($route_requirements['_scheme']); } - $path = $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route_requirements, $compiledRoute->getTokens(), $parameters, $name, FALSE, $hostTokens); + $path = $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route_requirements, $compiledRoute->getTokens(), $parameters, $route->getPath(), FALSE, $hostTokens); // The URL returned from doGenerate() will include the base path if there is // one (i.e., if running in a subdirectory) so we need to strip that off @@ -137,8 +150,18 @@ public function generate($name, $parameters = array(), $absolute = FALSE) { if (!empty($base_url) && strpos($path, $base_url) === 0) { $path = substr($path, strlen($base_url)); } + return $path; + } + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $absolute = FALSE) { + $route = $this->getRoute($name, $parameters); + $path = $this->getInternalPathFromRoute($route, $parameters); $path = $this->processPath($path); + + $base_url = $this->context->getBaseUrl(); if (!$absolute || !$host = $this->context->getHost()) { return $base_url . $path; } @@ -146,6 +169,7 @@ public function generate($name, $parameters = array(), $absolute = FALSE) { // Prepare an absolute URL by getting the correct scheme, host and port from // the request context. $scheme = $this->context->getScheme(); + $scheme_req = $route->getRequirement('_scheme'); if (isset($scheme_req) && ($req = strtolower($scheme_req)) && $scheme !== $req) { $scheme = $req; } @@ -356,4 +380,31 @@ protected function initialized() { return isset($this->basePath) && isset($this->baseUrl) && isset($this->scriptPath); } + /** + * Find the route using the provided route name (and parameters). + * + * @param string $name + * The route name to fetch + * @param array $parameters + * The parameters as they are passed to the UrlGeneratorInterface::generate + * call. + * + * @return \Symfony\Component\Routing\Route + * The found route. + * + * @throws \Symfony\Component\Routing\Exception\RouteNotFoundException + * Thrown if there is no route with that name in this repository. + * + * @see \Drupal\Core\Routing\RouteProviderInterface + */ + protected function getRoute($name, $parameters) { + if ($name instanceof SymfonyRoute) { + $route = $name; + } + elseif (NULL === $route = $this->provider->getRouteByName($name, $parameters)) { + throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name)); + } + return $route; + } + } diff --git a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php index 079213e6a9c2737861474ac4e8fff7226c58c58a..d6006ad3388efb2e539c5348c39cff124cec677f 100644 --- a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php @@ -30,6 +30,11 @@ */ class UrlGeneratorTest extends UnitTestCase { + /** + * The url generator to test. + * + * @var \Drupal\Core\Routing\UrlGenerator + */ protected $generator; protected $aliasManager; @@ -47,24 +52,50 @@ function setUp() { $routes = new RouteCollection(); $first_route = new Route('/test/one'); $second_route = new Route('/test/two/{narf}'); + $third_route = new Route('/test/two/'); + $fourth_route = new Route('/test/four', array(), array('_scheme' => 'https')); $routes->add('test_1', $first_route); $routes->add('test_2', $second_route); + $routes->add('test_3', $third_route); + $routes->add('test_4', $fourth_route); // Create a route provider stub. $provider = $this->getMockBuilder('Drupal\Core\Routing\RouteProvider') ->disableOriginalConstructor() ->getMock(); - $route_name_return_map = array( - array('test_1', array(), $first_route), - array('test_2', array('narf' => '5'), $second_route), + // We need to set up return value maps for both the getRouteByName() and the + // getRoutesByNames() method calls on the route provider. The parameters + // passed in will be slightly different but based on the same information. + $route_name_return_map = $routes_names_return_map = array(); + $return_map_values = array( + array( + 'route_name' => 'test_1', + 'parameters' => array(), + 'return' => $first_route, + ), + array( + 'route_name' => 'test_2', + 'parameters' => array('narf' => '5'), + 'return' => $second_route, + ), + array( + 'route_name' => 'test_3', + 'parameters' => array(), + 'return' => $third_route, + ), + array( + 'route_name' => 'test_4', + 'parameters' => array(), + 'return' => $fourth_route, + ), ); + foreach ($return_map_values as $values) { + $route_name_return_map[] = array($values['route_name'], $values['parameters'], $values['return']); + $routes_names_return_map[] = array(array($values['route_name']), $values['parameters'], $values['return']); + } $provider->expects($this->any()) ->method('getRouteByName') ->will($this->returnValueMap($route_name_return_map)); - $routes_names_return_map = array( - array(array('test_1'), array(), array($first_route)), - array(array('test_2'), array('narf' => '5'), array($second_route)), - ); $provider->expects($this->any()) ->method('getRoutesByNames') ->will($this->returnValueMap($routes_names_return_map)); @@ -73,14 +104,10 @@ function setUp() { $alias_manager = $this->getMockBuilder('Drupal\Core\Path\AliasManager') ->disableOriginalConstructor() ->getMock(); - $alias_map = array( - array('test/one', NULL, 'hello/world'), - array('test/two/5', NULL, 'goodbye/cruel/world'), - array('node/123', NULL, 'node/123'), - ); + $alias_manager->expects($this->any()) ->method('getPathAlias') - ->will($this->returnValueMap($alias_map)); + ->will($this->returnCallback(array($this, 'aliasManagerCallback'))); $this->aliasManager = $alias_manager; @@ -99,12 +126,48 @@ function setUp() { $this->generator = $generator; } + /** + * Return value callback for the getPathAlias() method on the mock alias manager. + * + * Ensures that by default the call to getPathAlias() will return the first argument + * that was passed in. We special-case the paths for which we wish it to return an + * actual alias. + * + * @return string + */ + public function aliasManagerCallback() { + $args = func_get_args(); + switch($args[0]) { + case 'test/one': + return 'hello/world'; + case 'test/two/5': + return 'goodbye/cruel/world'; + case '': + return ''; + default: + return $args[0]; + } + } + /** * Confirms that generated routes will have aliased paths. */ public function testAliasGeneration() { $url = $this->generator->generate('test_1'); $this->assertEquals('/hello/world', $url); + + $path = $this->generator->getPathFromRoute('test_1'); + $this->assertEquals('test/one', $path); + } + + /** + * Tests URL generation in a subdirectory. + */ + public function testGetPathFromRouteWithSubdirectory() { + $this->generator->setBasePath('/test-base-path'); + + $path = $this->generator->getPathFromRoute('test_1'); + $this->assertEquals('test/one', $path); } /** @@ -113,6 +176,17 @@ public function testAliasGeneration() { public function testAliasGenerationWithParameters() { $url = $this->generator->generate('test_2', array('narf' => '5')); $this->assertEquals('/goodbye/cruel/world', $url, 'Correct URL generated including alias and parameters.'); + + $path = $this->generator->getPathFromRoute('test_2', array('narf' => '5')); + $this->assertEquals('test/two/5', $path); + } + + /** + * Tests URL generation from route with trailing start and end slashes. + */ + public function testGetPathFromRouteTrailing() { + $path = $this->generator->getPathFromRoute('test_3'); + $this->assertEquals($path, 'test/two'); } /** @@ -123,6 +197,14 @@ public function testAbsoluteURLGeneration() { $this->assertEquals('http://localhost/hello/world', $url); } + /** + * Test that the 'scheme' route requirement is respected during url generation. + */ + public function testUrlGenerationWithHttpsRequirement() { + $url = $this->generator->generate('test_4', array(), TRUE); + $this->assertEquals('https://localhost/test/four', $url); + } + /** * Tests path-based URL generation. */ @@ -136,7 +218,6 @@ public function testPathBasedURLGeneration() { foreach (array(FALSE, TRUE) as $absolute) { // Get the expected start of the path string. $base = ($absolute ? $base_url . '/' : $base_path . '/') . $script_path; - $absolute_string = $absolute ? 'absolute' : NULL; $url = $base . 'node/123'; $result = $this->generator->generateFromPath('node/123', array('absolute' => $absolute)); $this->assertEquals($url, $result, "$url == $result");