getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') ->disableOriginalConstructor() ->getMock(); $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); $container = new ContainerBuilder(); $container->set('cache_contexts_manager', $cache_contexts_manager); \Drupal::setContainer($container); $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', [], [], [], '', ['https']); $none_route = new Route('', [], [], ['_no_path' => TRUE]); $routes->add('test_1', $first_route); $routes->add('test_2', $second_route); $routes->add('test_3', $third_route); $routes->add('test_4', $fourth_route); $routes->add('', $none_route); // Create a route provider stub. $provider = $this->getMockBuilder('Drupal\Core\Routing\RouteProvider') ->disableOriginalConstructor() ->getMock(); // We need to set up return value maps for both the getRouteByName() and the // getRoutesByNames() method calls on the route provider. The parameters // are not passed in and default to an empty array. $route_name_return_map = $routes_names_return_map = []; $return_map_values = [ [ 'route_name' => 'test_1', 'return' => $first_route, ], [ 'route_name' => 'test_2', 'return' => $second_route, ], [ 'route_name' => 'test_3', 'return' => $third_route, ], [ 'route_name' => 'test_4', 'return' => $fourth_route, ], [ 'route_name' => '', 'return' => $none_route, ], ]; foreach ($return_map_values as $values) { $route_name_return_map[] = [$values['route_name'], $values['return']]; $routes_names_return_map[] = [[$values['route_name']], $values['return']]; } $this->provider = $provider; $this->provider->expects($this->any()) ->method('getRouteByName') ->will($this->returnValueMap($route_name_return_map)); $provider->expects($this->any()) ->method('getRoutesByNames') ->will($this->returnValueMap($routes_names_return_map)); // Create an alias manager stub. $alias_manager = $this->getMockBuilder('Drupal\Core\Path\AliasManager') ->disableOriginalConstructor() ->getMock(); $alias_manager->expects($this->any()) ->method('getAliasByPath') ->will($this->returnCallback([$this, 'aliasManagerCallback'])); $this->aliasManager = $alias_manager; $this->requestStack = new RequestStack(); $request = Request::create('/some/path'); $this->requestStack->push($request); $this->context = new RequestContext(); $this->context->fromRequestStack($this->requestStack); $processor = new PathProcessorAlias($this->aliasManager); $processor_manager = new PathProcessorManager(); $processor_manager->addOutbound($processor, 1000); $this->processorManager = $processor_manager; $this->routeProcessorManager = $this->getMockBuilder('Drupal\Core\RouteProcessor\RouteProcessorManager') ->disableOriginalConstructor() ->getMock(); $generator = new UrlGenerator($this->provider, $processor_manager, $this->routeProcessorManager, $this->requestStack, ['http', 'https']); $generator->setContext($this->context); $this->generator = $generator; } /** * Return value callback for the getAliasByPath() method on the mock alias * manager. * * Ensures that by default the call to getAliasByPath() 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); // No cacheability to test; UrlGenerator::generate() doesn't support // collecting cacheability metadata. $this->routeProcessorManager->expects($this->exactly(3)) ->method('processOutbound') ->with($this->anything()); // Check that the two generate methods return the same result. $this->assertGenerateFromRoute('test_1', [], [], $url, (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); $path = $this->generator->getPathFromRoute('test_1'); $this->assertEquals('test/one', $path); } /** * Confirms that generated routes will have aliased paths using interface constants. */ public function testAliasGenerationUsingInterfaceConstants() { $url = $this->generator->generate('test_1', [], UrlGenerator::ABSOLUTE_PATH); $this->assertEquals('/hello/world', $url); // No cacheability to test; UrlGenerator::generate() doesn't support // collecting cacheability metadata. $this->routeProcessorManager->expects($this->exactly(3)) ->method('processOutbound') ->with($this->anything()); // Check that the two generate methods return the same result. $this->assertGenerateFromRoute('test_1', [], [], $url, (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); $path = $this->generator->getPathFromRoute('test_1'); $this->assertEquals('test/one', $path); } /** * @covers ::generateFromRoute */ public function testUrlGenerationWithDisabledPathProcessing() { $path_processor = $this->prophesize(OutboundPathProcessorInterface::class); $path_processor->processOutbound(Argument::cetera())->shouldNotBeCalled(); $generator = new UrlGenerator($this->provider, $path_processor->reveal(), $this->routeProcessorManager, $this->requestStack, ['http', 'https']); $generator->setContext($this->context); $url = $this->generator->generateFromRoute('test_1', [], ['path_processing' => FALSE]); $this->assertEquals('/test/one', $url); } /** * @covers ::generateFromRoute */ public function testUrlGenerationWithDisabledPathProcessingByRoute() { $path_processor = $this->prophesize(OutboundPathProcessorInterface::class); $path_processor->processOutbound(Argument::cetera())->shouldNotBeCalled(); $provider = $this->prophesize(RouteProviderInterface::class); $provider->getRouteByName('test_1')->willReturn(new Route('/test/one', [], [], ['default_url_options' => ['path_processing' => FALSE]])); $generator = new UrlGenerator($provider->reveal(), $path_processor->reveal(), $this->routeProcessorManager, $this->requestStack, ['http', 'https']); $generator->setContext($this->context); $url = $generator->generateFromRoute('test_1', []); $this->assertEquals('/test/one', $url); } /** * @covers ::generateFromRoute */ public function testUrlGenerationWithDisabledPathProcessingByRouteAndOptedInPathProcessing() { $path_processor = $this->prophesize(OutboundPathProcessorInterface::class); $path_processor->processOutbound('/test/one', Argument::cetera())->willReturn('/hello/world')->shouldBeCalled(); $provider = $this->prophesize(RouteProviderInterface::class); $provider->getRouteByName('test_1')->willReturn(new Route('/test/one', [], [], ['default_url_options' => ['path_processing' => FALSE]])); $generator = new UrlGenerator($provider->reveal(), $path_processor->reveal(), $this->routeProcessorManager, $this->requestStack, ['http', 'https']); $generator->setContext($this->context); $url = $generator->generateFromRoute('test_1', [], ['path_processing' => TRUE]); $this->assertEquals('/hello/world', $url); } /** * Tests URL generation in a subdirectory. */ public function testGetPathFromRouteWithSubdirectory() { $this->routeProcessorManager->expects($this->once()) ->method('processOutbound'); $path = $this->generator->getPathFromRoute('test_1'); $this->assertEquals('test/one', $path); } /** * Confirms that generated routes will have aliased paths. */ public function testAliasGenerationWithParameters() { $url = $this->generator->generate('test_2', ['narf' => '5']); $this->assertEquals('/goodbye/cruel/world', $url); // No cacheability to test; UrlGenerator::generate() doesn't support // collecting cacheability metadata. $this->routeProcessorManager->expects($this->any()) ->method('processOutbound') ->with($this->anything()); $options = ['fragment' => 'top']; // Extra parameters should appear in the query string. $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, '/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); $options = ['query' => ['page' => '1'], 'fragment' => 'bottom']; $this->assertGenerateFromRoute('test_2', ['narf' => 5], $options, '/goodbye/cruel/world?page=1#bottom', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); // Changing the parameters, the route still matches but there is no alias. $this->assertGenerateFromRoute('test_2', ['narf' => 7], $options, '/test/two/7?page=1#bottom', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); $path = $this->generator->getPathFromRoute('test_2', ['narf' => '5']); $this->assertEquals('test/two/5', $path); // Specify a query parameter with NULL. $options = ['query' => ['page' => NULL], 'fragment' => 'bottom']; $this->assertGenerateFromRoute('test_2', ['narf' => 5], $options, '/goodbye/cruel/world?page#bottom', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); } /** * Confirms that generated routes will have aliased paths with options. * * @dataProvider providerTestAliasGenerationWithOptions */ public function testAliasGenerationWithOptions($route_name, $route_parameters, $options, $expected) { $this->assertGenerateFromRoute($route_name, $route_parameters, $options, $expected, (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); } /** * Provides test data for testAliasGenerationWithOptions. */ public function providerTestAliasGenerationWithOptions() { $data = []; // Extra parameters should appear in the query string. $data[] = [ 'test_1', ['zoo' => '5'], ['fragment' => 'top'], '/hello/world?zoo=5#top', ]; $data[] = [ 'test_2', ['narf' => '5'], ['query' => ['page' => '1'], 'fragment' => 'bottom'], '/goodbye/cruel/world?page=1#bottom', ]; // Changing the parameters, the route still matches but there is no alias. $data[] = [ 'test_2', ['narf' => '7'], ['query' => ['page' => '1'], 'fragment' => 'bottom'], '/test/two/7?page=1#bottom', ]; // Query string values containing '/' should be decoded. $data[] = [ 'test_2', ['narf' => '7'], ['query' => ['page' => '1/2'], 'fragment' => 'bottom'], '/test/two/7?page=1/2#bottom', ]; // A NULL query string. $data['query-with-NULL'] = [ 'test_2', ['narf' => '7'], ['query' => NULL, 'fragment' => 'bottom'], '/test/two/7#bottom', ]; return $data; } /** * Tests URL generation from route with trailing start and end slashes. */ public function testGetPathFromRouteTrailing() { $this->routeProcessorManager->expects($this->once()) ->method('processOutbound'); $path = $this->generator->getPathFromRoute('test_3'); $this->assertEquals($path, 'test/two'); } /** * Confirms that absolute URLs work with generated routes. */ public function testAbsoluteURLGeneration() { $url = $this->generator->generate('test_1', [], TRUE); $this->assertEquals('http://localhost/hello/world', $url); // No cacheability to test; UrlGenerator::generate() doesn't support // collecting cacheability metadata. $this->routeProcessorManager->expects($this->exactly(2)) ->method('processOutbound') ->with($this->anything()); $options = ['absolute' => TRUE, 'fragment' => 'top']; // Extra parameters should appear in the query string. $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, 'http://localhost/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheContexts(['url.site'])); } /** * Confirms that absolute URLs work with generated routes using interface constants. */ public function testAbsoluteURLGenerationUsingInterfaceConstants() { $url = $this->generator->generate('test_1', [], UrlGenerator::ABSOLUTE_URL); $this->assertEquals('http://localhost/hello/world', $url); // No cacheability to test; UrlGenerator::generate() doesn't support // collecting cacheability metadata. $this->routeProcessorManager->expects($this->exactly(2)) ->method('processOutbound') ->with($this->anything()); $options = ['absolute' => TRUE, 'fragment' => 'top']; // Extra parameters should appear in the query string. $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, 'http://localhost/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheContexts(['url.site'])); } /** * Confirms that explicitly setting the base_url works with generated routes */ public function testBaseURLGeneration() { $options = ['base_url' => 'http://www.example.com:8888']; $this->assertGenerateFromRoute('test_1', [], $options, 'http://www.example.com:8888/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); $options = ['base_url' => 'http://www.example.com:8888', 'https' => TRUE]; $this->assertGenerateFromRoute('test_1', [], $options, 'https://www.example.com:8888/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); $options = ['base_url' => 'https://www.example.com:8888', 'https' => FALSE]; $this->assertGenerateFromRoute('test_1', [], $options, 'http://www.example.com:8888/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); $this->routeProcessorManager->expects($this->exactly(2)) ->method('processOutbound') ->with($this->anything()); $options = ['base_url' => 'http://www.example.com:8888', 'fragment' => 'top']; // Extra parameters should appear in the query string. $this->assertGenerateFromRoute('test_1', ['zoo' => 5], $options, 'http://www.example.com:8888/hello/world?zoo=5#top', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); } /** * Test that the 'scheme' route requirement is respected during url generation. */ public function testUrlGenerationWithHttpsRequirement() { $url = $this->generator->generate('test_4', [], TRUE); $this->assertEquals('https://localhost/test/four', $url); // No cacheability to test; UrlGenerator::generate() doesn't support // collecting cacheability metadata. $this->routeProcessorManager->expects($this->exactly(2)) ->method('processOutbound') ->with($this->anything()); $options = ['absolute' => TRUE, 'https' => TRUE]; $this->assertGenerateFromRoute('test_1', [], $options, 'https://localhost/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheContexts(['url.site'])); } /** * Tests generating a relative URL with no path. * * @param array $options * An array of URL options. * @param string $expected_url * The expected relative URL. * * @covers ::generateFromRoute * * @dataProvider providerTestNoPath */ public function testNoPath($options, $expected_url) { $url = $this->generator->generateFromRoute('', [], $options); $this->assertEquals($expected_url, $url); } /** * Data provider for ::testNoPath(). */ public function providerTestNoPath() { return [ // Empty options. [[], ''], // Query parameters only. [['query' => ['foo' => 'bar']], '?foo=bar'], // Multiple query parameters. [['query' => ['foo' => 'bar', 'baz' => '']], '?foo=bar&baz='], // Fragment only. [['fragment' => 'foo'], '#foo'], // Query parameters and fragment. [['query' => ['bar' => 'baz'], 'fragment' => 'foo'], '?bar=baz#foo'], // Multiple query parameters and fragment. [['query' => ['bar' => 'baz', 'foo' => 'bar'], 'fragment' => 'foo'], '?bar=baz&foo=bar#foo'], ]; } /** * @covers \Drupal\Core\Routing\UrlGenerator::generateFromRoute * * Note: We use absolute covers to let * \Drupal\Tests\Core\Render\MetadataBubblingUrlGeneratorTest work. */ public function testGenerateWithPathProcessorChangingQueryParameter() { $path_processor = $this->getMock(OutboundPathProcessorInterface::CLASS); $path_processor->expects($this->atLeastOnce()) ->method('processOutbound') ->willReturnCallback(function ($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { $options['query'] = ['zoo' => 5]; return $path; }); $this->processorManager->addOutbound($path_processor); $options = []; $this->assertGenerateFromRoute('test_2', ['narf' => 5], $options, '/goodbye/cruel/world?zoo=5', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)); } /** * Asserts \Drupal\Core\Routing\UrlGenerator::generateFromRoute()'s output. * * @param $route_name * The route name to test. * @param array $route_parameters * The route parameters to test. * @param array $options * The options to test. * @param $expected_url * The expected generated URL string. * @param \Drupal\Core\Render\BubbleableMetadata $expected_bubbleable_metadata * The expected generated bubbleable metadata. */ protected function assertGenerateFromRoute($route_name, array $route_parameters, array $options, $expected_url, BubbleableMetadata $expected_bubbleable_metadata) { // First, test with $collect_cacheability_metadata set to the default value. $url = $this->generator->generateFromRoute($route_name, $route_parameters, $options); $this->assertSame($expected_url, $url); // Second, test with it set to TRUE. $generated_url = $this->generator->generateFromRoute($route_name, $route_parameters, $options, TRUE); $this->assertSame($expected_url, $generated_url->getGeneratedUrl()); $this->assertEquals($expected_bubbleable_metadata, BubbleableMetadata::createFromObject($generated_url)); } }