diff --git a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php index e7dcd35..cd6b2c1 100644 --- a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php +++ b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php @@ -133,25 +133,23 @@ public function build(array $attributes) { // Copy the path elements for up-casting. $route_request = $this->getRequestForPath(implode('/', $path_elements), $exclude); if ($route_request) { - if (!$route_request->attributes->get('_legacy')) { - $route_name = $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME); - // Note that the parameters don't really matter here since we're - // passing in the request which already has the upcast attributes. - $parameters = array(); - $access = $this->accessManager->checkNamedRoute($route_name, $parameters, \Drupal::currentUser(), $route_request); - if ($access) { - $title = $this->titleResolver->getTitle($route_request, $route_request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); - } + $route_name = $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME); + // Note that the parameters don't really matter here since we're + // passing in the request which already has the upcast attributes. + $parameters = array(); + $access = $this->accessManager->checkNamedRoute($route_name, $parameters, $this->getCurrentUser(), $route_request); + if ($access) { + $title = $this->titleResolver->getTitle($route_request, $route_request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); } if ($access) { - if (!$title) { + if (!isset($title)) { // Fallback to using the raw path component as the title if the // route is missing a _title or _title_callback attribute. $title = str_replace(array('-', '_'), ' ', Unicode::ucfirst(end($path_elements))); } // @todo Replace with a #type => link render element so that the alter // hook can work with the actual data. - $links[] = l($title, $route_request->attributes->get('_system_path')); + $links[] = $this->l($title, $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME), $route_request->attributes->get('_raw_variables')->all()); } } @@ -204,4 +202,13 @@ protected function getRequestForPath($path, array $exclude) { } } + /** + * Gets the current user. + * + * @return \Drupal\Core\Session\AccountInterface + */ + protected function getCurrentUser() { + return \Drupal::currentUser(); + } + } diff --git a/core/modules/system/tests/Drupal/system/Tests/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php b/core/modules/system/tests/Drupal/system/Tests/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php index bcf0a41..0abdf13 100644 --- a/core/modules/system/tests/Drupal/system/Tests/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php +++ b/core/modules/system/tests/Drupal/system/Tests/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php @@ -7,14 +7,26 @@ namespace Drupal\system\Tests\Breadcrumbs; +use Drupal\Core\ParamConverter\ParamNotConvertedException; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Core\Utility\LinkGeneratorInterface; use Drupal\system\PathBasedBreadcrumbBuilder; use Drupal\Tests\UnitTestCase; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Route; /** * Tests machine name controller's transliteration functionality. * * @group System + * @group Drupal + * + * @coversDefaultClass \Drupal\system\PathBasedBreadcrumbBuilder */ class PathBasedBreadcrumbBuilderTest extends UnitTestCase { @@ -26,6 +38,20 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase { protected $builder; /** + * The title resolver. + * + * @var \Drupal\Core\Controller\TitleResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $titleResolver; + + /** + * The mocked access manager. + * + * @var \Drupal\Core\Access\AccessManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $accessManager; + + /** * The request matching mock object. * * @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface|\PHPUnit_Framework_MockObject_MockObject @@ -33,6 +59,34 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase { protected $requestMatcher; /** + * The mocked request object. + * + * @var \Symfony\Component\HttpFoundation\Request|\PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + + /** + * The mocked link generator. + * + * @var \Drupal\Core\Utility\LinkGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $linkGenerator; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $currentUser; + + /** + * The mocked path processor. + * + * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $pathProcessor; + + /** * {@inheritdoc} */ public static function getInfo() { @@ -45,47 +99,374 @@ public static function getInfo() { /** * {@inheritdoc} + * + * @covers ::__construct() */ public function setUp() { parent::setUp(); - $this->requestMatcher = $this->getMockBuilder('\Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $this->requestMatcher = $this->getMock('\Symfony\Component\Routing\Matcher\RequestMatcherInterface'); - $config_factory = $this->getMockBuilder('\Drupal\Core\Config\ConfigFactoryInterface')->getMock(); - $config_factory->expects($this->any()) - ->method('get') - ->will($this->returnValue('test')); + $config_factory = $this->getConfigFactoryStub(array('system.site' => array('front' => 'test_frontpage'))); - $processor = $this->getMockBuilder('\Drupal\Core\PathProcessor\InboundPathProcessorInterface')->getMock(); - $processor->expects($this->once()) - ->method('processInbound') - ->will($this->returnValue('test')); + $this->pathProcessor = $this->getMock('\Drupal\Core\PathProcessor\InboundPathProcessorInterface'); + $this->request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->getMock(); - $this->builder = new PathBasedBreadcrumbBuilder( - $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('\Drupal\Core\Entity\EntityManagerInterface')->getMock(), - $this->getMockBuilder('\Drupal\Core\Access\AccessManager')->disableOriginalConstructor()->getMock(), + $this->accessManager = $this->getMockBuilder('\Drupal\Core\Access\AccessManager') + ->disableOriginalConstructor()->getMock(); + $this->titleResolver = $this->getMock('\Drupal\Core\Controller\TitleResolverInterface'); + $this->builder = new TestPathBasedBreadcrumbBuilder( + $this->request, + $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'), + $this->accessManager, $this->requestMatcher, - $processor, + $this->pathProcessor, $config_factory, - $this->getMockBuilder('\Drupal\Core\Controller\TitleResolverInterface')->getMock() + $this->titleResolver ); + + $this->builder->setTranslationManager($this->getStringTranslationStub()); + + $this->linkGenerator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'); + $this->builder->setLinkGenerator($this->linkGenerator); + + $this->currentUser = $this->getMock('Drupal\Core\Session\AccountInterface'); + $this->builder->setCurrentUser($this->currentUser); + } + + /** + * Tests the build method on the frontpage. + * + * @covers ::build() + */ + public function testBuildOnFrontpage() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/')); + + $links = $this->builder->build(array()); + $this->assertEquals(array(), $links); + } + + /** + * Tests the build method with one path element. + * + * @covers ::build() + */ + public function testBuildWithOnePathElement() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/example')); + + $this->setupLinkGeneratorWithFrontpage(); + + $links = $this->builder->build(array()); + $this->assertEquals(array(0 => 'Home'), $links); + } + + /** + * Tests the build method with two path elements. + * + * @covers ::build() + * @covers ::getRequestForPath() + */ + public function testBuildWithTwoPathElements() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/example/baz')); + $this->setupStubPathProcessor(); + + $route_1 = new Route('/example'); + + $this->requestMatcher->expects($this->exactly(1)) + ->method('matchRequest') + ->will($this->returnCallback(function(Request $request) use ($route_1) { + if ($request->getPathInfo() == '/example') { + return array( + RouteObjectInterface::ROUTE_NAME => 'example', + RouteObjectInterface::ROUTE_OBJECT => $route_1, + '_raw_variables' => new ParameterBag(array()), + ); + } + })); + + $link_example = 'Example'; + $link_front = 'Home'; + $this->linkGenerator->expects($this->at(0)) + ->method('generate') + ->with('Example', 'example', array(), array()) + ->will($this->returnValue($link_example)); + $this->linkGenerator->expects($this->at(1)) + ->method('generate') + ->with('Home', '', array(), array()) + ->will($this->returnValue($link_front)); + $this->setupAccessManagerWithTrue(); + + $links = $this->builder->build(array()); + $this->assertEquals(array(0 => 'Home', 1 => $link_example), $links); + } + + /** + * Tests the build method with three path elements. + * + * @covers ::build() + * @covers ::getRequestForPath() + */ + public function testBuildWithThreePathElements() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/example/bar/baz')); + $this->setupStubPathProcessor(); + + $route_1 = new Route('/example/bar'); + $route_2 = new Route('/example'); + + $this->requestMatcher->expects($this->exactly(2)) + ->method('matchRequest') + ->will($this->returnCallback(function(Request $request) use ($route_1, $route_2) { + if ($request->getPathInfo() == '/example/bar') { + return array( + RouteObjectInterface::ROUTE_NAME => 'example_bar', + RouteObjectInterface::ROUTE_OBJECT => $route_1, + '_raw_variables' => new ParameterBag(array()), + ); + } + elseif ($request->getPathInfo() == '/example') { + return array( + RouteObjectInterface::ROUTE_NAME => 'example', + RouteObjectInterface::ROUTE_OBJECT => $route_2, + '_raw_variables' => new ParameterBag(array()), + ); + } + })); + + $link_example_bar = 'Bar'; + $link_example = 'Example'; + $link_front = 'Home'; + $this->linkGenerator->expects($this->at(0)) + ->method('generate') + ->with('Bar', 'example_bar', array(), array()) + ->will($this->returnValue($link_example_bar)); + + $this->linkGenerator->expects($this->at(1)) + ->method('generate') + ->with('Example', 'example', array(), array()) + ->will($this->returnValue($link_example)); + $this->linkGenerator->expects($this->at(2)) + ->method('generate') + ->with('Home', '', array(), array()) + ->will($this->returnValue($link_front)); + $this->setupAccessManagerWithTrue(); + + $links = $this->builder->build(array()); + $this->assertEquals(array(0 => 'Home', 1 => $link_example, 2 => $link_example_bar), $links); } /** * Tests that MethodNotAllowedExceptions are caught. + * + * @covers ::build() + * @covers ::getRequestForPath() */ - public function testPathMatchingExceptions() { + public function testBuildWithPathMatchingExceptions() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/example/bar')); + $this->setupStubPathProcessor(); + $this->requestMatcher->expects($this->any()) ->method('matchRequest') ->will($this->throwException(new MethodNotAllowedException(array()))); + $this->setupLinkGeneratorWithFrontpage(); + + $links = $this->builder->build(array()); + + // No path matched, though at least the frontpage is displayed. + $this->assertEquals(array(0 => 'Home'), $links); + } + + /** + * Tests that ParamNotConvertedException are caught. + * + * @covers ::build() + * @covers ::getRequestForPath() + */ + public function testBuildWithParamNotConvertedException() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/example/bar')); + $this->setupStubPathProcessor(); + + $this->requestMatcher->expects($this->any()) + ->method('matchRequest') + ->will($this->throwException(new ParamNotConvertedException())); + $this->setupLinkGeneratorWithFrontpage(); + + $links = $this->builder->build(array()); + + // No path matched, though at least the frontpage is displayed. + $this->assertEquals(array(0 => 'Home'), $links); + } + + /** + * Tests the build method with a non processed path. + * + * @covers ::build() + * @covers ::getRequestForPath() + */ + public function testBuildWithNonProcessedPath() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/example/bar')); + + $this->pathProcessor->expects($this->once()) + ->method('processInbound') + ->will($this->returnValue(FALSE)); + + $this->requestMatcher->expects($this->any()) + ->method('matchRequest') + ->will($this->returnValue(array())); + $this->setupLinkGeneratorWithFrontpage(); + + $links = $this->builder->build(array()); - // Test the protected method of the class by making it accessible with - // reflection. - $method = new \ReflectionMethod('\Drupal\system\PathBasedBreadcrumbBuilder', 'getRequestForPath'); - $method->setAccessible(true); + // No path matched, though at least the frontpage is displayed. + $this->assertEquals(array(0 => 'Home'), $links); + } + + + /** + * Tests that ResourceNotFoundException are caught. + * + * @covers ::build() + * @covers ::getRequestForPath() + */ + public function testBuildWithRosourceNotFoundException() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/example/bar')); + $this->setupStubPathProcessor(); + + $this->requestMatcher->expects($this->any()) + ->method('matchRequest') + ->will($this->throwException(new ResourceNotFoundException())); + $this->setupLinkGeneratorWithFrontpage(); - $method->invoke($this->builder, 'test', array()); + $links = $this->builder->build(array()); + + // No path matched, though at least the frontpage is displayed. + $this->assertEquals(array(0 => 'Home'), $links); + } + + /** + * Tests the applied method. + * + * @covers ::applies() + */ + public function testApplies() { + $this->assertTrue($this->builder->applies(array())); + } + + /** + * Tests the breadcrumb for an user path. + * + * @covers ::build() + * @covers ::getRequestForPath() + */ + public function testBuildWithUserPath() { + $this->request->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/user/1/edit')); + $this->setupStubPathProcessor(); + + $route_1 = new Route('/user/1'); + + $this->requestMatcher->expects($this->exactly(1)) + ->method('matchRequest') + ->will($this->returnCallback(function(Request $request) use ($route_1) { + if ($request->getPathInfo() == '/user/1') { + return array( + RouteObjectInterface::ROUTE_NAME => 'user_page', + RouteObjectInterface::ROUTE_OBJECT => $route_1, + '_raw_variables' => new ParameterBag(array()), + ); + } + })); + + $link_user = 'Admin'; + $link_front = 'Home'; + $this->linkGenerator->expects($this->at(0)) + ->method('generate') + ->with('Admin', 'user_page', array(), array()) + ->will($this->returnValue($link_user)); + + $this->linkGenerator->expects($this->at(1)) + ->method('generate') + ->with('Home', '', array(), array()) + ->will($this->returnValue($link_front)); + $this->setupAccessManagerWithTrue(); + $this->titleResolver->expects($this->once()) + ->method('getTitle') + ->with($this->anything(), $route_1) + ->will($this->returnValue('Admin')); + + $links = $this->builder->build(array()); + $this->assertEquals(array(0 => 'Home', 1 => $link_user), $links); + } + + /** + * Setup the link generator with a frontpage route. + */ + public function setupLinkGeneratorWithFrontpage() { + $this->linkGenerator->expects($this->once()) + ->method('generate') + ->with($this->anything(), '', array()) + ->will($this->returnCallback(function($title) { + return '' . $title . ''; + })); + } + + /** + * Setup the access manager to always return TRUE. + */ + public function setupAccessManagerWithTrue() { + $this->accessManager->expects($this->any()) + ->method('checkNamedRoute') + ->will($this->returnValue(TRUE)); + } + + protected function setupStubPathProcessor() { + $this->pathProcessor->expects($this->any()) + ->method('processInbound') + ->will($this->returnArgument(0)); } } + +class TestPathBasedBreadcrumbBuilder extends PathBasedBreadcrumbBuilder { + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + public function setTranslationManager(TranslationInterface $translation_manager) { + $this->translationManager = $translation_manager; + } + + public function setLinkGenerator(LinkGeneratorInterface $link_generator) { + $this->linkGenerator = $link_generator; + } + + public function setCurrentUser(AccountInterface $current_user) { + $this->currentUser = $current_user; + } + + protected function getCurrentUser() { + return $this->currentUser; + } + +} +