diff --git a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
index 091ec9c..99e1b05 100644
--- a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
+++ b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
@@ -18,6 +18,7 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
@@ -132,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());
}
}
@@ -171,7 +170,7 @@ public function build(array $attributes) {
* An array of paths or system paths to skip.
*
* @return \Symfony\Component\HttpFoundation\Request
- * A populated request object or NULL if the patch couldn't be matched.
+ * A populated request object or NULL if the path couldn't be matched.
*/
protected function getRequestForPath($path, array $exclude) {
if (!empty($exclude[$path])) {
@@ -198,6 +197,19 @@ protected function getRequestForPath($path, array $exclude) {
catch (ResourceNotFoundException $e) {
return NULL;
}
+ catch (MethodNotAllowedException $e) {
+ return NULL;
+ }
+ }
+
+ /**
+ * Gets the current user.
+ *
+ * @return \Drupal\Core\Session\AccountInterface
+ * The current user object.
+ */
+ 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
new file mode 100644
index 0000000..d2c4771
--- /dev/null
+++ b/core/modules/system/tests/Drupal/system/Tests/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
@@ -0,0 +1,442 @@
+ 'Path based breadcrumbs',
+ 'description' => 'Tests that path based breadcrumbs work as expected.',
+ 'group' => 'Breadcrumbs',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @covers ::__construct()
+ */
+ public function setUp() {
+ parent::setUp();
+
+ $this->requestMatcher = $this->getMock('\Symfony\Component\Routing\Matcher\RequestMatcherInterface');
+
+ $config_factory = $this->getConfigFactoryStub(array('system.site' => array('front' => 'test_frontpage')));
+
+ $this->pathProcessor = $this->getMock('\Drupal\Core\PathProcessor\InboundPathProcessorInterface');
+ $this->request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->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,
+ $this->pathProcessor,
+ $config_factory,
+ $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 exceptions during request matching are caught.
+ *
+ * @covers ::build()
+ * @covers ::getRequestForPath()
+ *
+ * @dataProvider providerTestBuildWithException
+ */
+ public function testBuildWithException($exception_class, $exception_argument) {
+ $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 $exception_class($exception_argument)));
+ $this->setupLinkGeneratorWithFrontpage();
+
+ $links = $this->builder->build(array());
+
+ // No path matched, though at least the frontpage is displayed.
+ $this->assertEquals(array(0 => 'Home'), $links);
+ }
+
+ /**
+ * Provides exception types for testBuildWithException.
+ *
+ * @return array
+ * The list of exception test cases.
+ *
+ * @see \Drupal\system\Tests\Breadcrumbs\PathBasedBreadcrumbBuilderTest::testBuildWithException()
+ */
+ public function providerTestBuildWithException() {
+ return array(
+ array('Drupal\Core\ParamConverter\ParamNotConvertedException', ''),
+ array('Symfony\Component\Routing\Exception\MethodNotAllowedException', array()),
+ array('Symfony\Component\Routing\Exception\ResourceNotFoundException', ''),
+ );
+ }
+
+ /**
+ * 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());
+
+ // 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 a 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));
+ }
+
+}
+
+/**
+ * Helper class for testing purposes only.
+ */
+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;
+ }
+
+}