diff --git a/core/core.services.yml b/core/core.services.yml index 0fe0a7f..9b3f72a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -39,6 +39,11 @@ services: arguments: ['@request_stack'] tags: - { name: cache.context } + cache_context.url.path: + class: Drupal\Core\Cache\PathCacheContext + arguments: ['@request_stack'] + tags: + - { name: cache.context } cache_context.url.query_args: class: Drupal\Core\Cache\Context\QueryArgsCacheContext arguments: ['@request_stack'] diff --git a/core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php b/core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php new file mode 100644 index 0000000..1a38f8b --- /dev/null +++ b/core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php @@ -0,0 +1,62 @@ +links; + } + + /** + * Sets the breadcrumb links. + * + * @param \Drupal\Core\Link[] $links + * The breadcrumb links. + * + * @return $this + */ + public function setLinks(array $links) { + $this->links = $links; + + return $this; + } + + /** + * Appends a link to the end of the ordered list of breadcrumb links. + * + * @param \Drupal\Core\Link $link + * The link appended to the breadcrumb. + * + * @return $this + */ + public function addLink(Link $link) { + $this->links[] = $link; + + return $this; + } + +} diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php index ebdfa55..e566f54 100644 --- a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php +++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php @@ -32,9 +32,8 @@ public function applies(RouteMatchInterface $route_match); * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The current route match. * - * @return \Drupal\Core\Link[] - * An array of links for the breadcrumb. Returning an empty array will - * suppress all breadcrumbs. + * @return \Drupal\Core\Breadcrumb\Breadcrumb + * A breadcrumb. */ public function build(RouteMatchInterface $route_match); diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php index 0099897..c233691 100644 --- a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php +++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php @@ -76,21 +76,28 @@ public function applies(RouteMatchInterface $route_match) { * {@inheritdoc} */ public function build(RouteMatchInterface $route_match) { - $breadcrumb = array(); + $build = new Breadcrumb(); + + // Without builders return early. + $sorted_builders = $this->getSortedBuilders(); + if (empty($sorted_builders)) { + return $build; + } + $context = array('builder' => NULL); // Call the build method of registered breadcrumb builders, // until one of them returns an array. - foreach ($this->getSortedBuilders() as $builder) { + foreach ($sorted_builders as $builder) { if (!$builder->applies($route_match)) { // The builder does not apply, so we continue with the other builders. continue; } - $build = $builder->build($route_match); + $breadcrumb = $builder->build($route_match); - if (is_array($build)) { - // The builder returned an array of breadcrumb links. - $breadcrumb = $build; + if ($breadcrumb instanceof Breadcrumb) { + $build->setCacheContexts($breadcrumb->getCacheContexts()); + $build->setLinks($breadcrumb->getLinks()); $context['builder'] = $builder; break; } @@ -99,9 +106,9 @@ public function build(RouteMatchInterface $route_match) { } } // Allow modules to alter the breadcrumb. - $this->moduleHandler->alter('system_breadcrumb', $breadcrumb, $route_match, $context); - // Fall back to an empty breadcrumb. - return $breadcrumb; + $this->moduleHandler->alter('system_breadcrumb', $build, $route_match, $context); + + return $build; } /** diff --git a/core/lib/Drupal/Core/Cache/PathCacheContext.php b/core/lib/Drupal/Core/Cache/PathCacheContext.php new file mode 100644 index 0000000..3fd3bc6 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/PathCacheContext.php @@ -0,0 +1,40 @@ +t('Path'); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + $request = $this->requestStack->getCurrentRequest(); + return $request->getBasePath() . $request->getPathInfo(); + } + +} diff --git a/core/lib/Drupal/Core/Menu/menu.api.php b/core/lib/Drupal/Core/Menu/menu.api.php index 1d8298f..67aa7c9 100644 --- a/core/lib/Drupal/Core/Menu/menu.api.php +++ b/core/lib/Drupal/Core/Menu/menu.api.php @@ -533,12 +533,8 @@ function hook_contextual_links_plugins_alter(array &$contextual_links) { /** * Perform alterations to the breadcrumb built by the BreadcrumbManager. * - * @param array $breadcrumb - * An array of breadcrumb link a tags, returned by the breadcrumb manager - * build method, for example - * @code - * array('Home'); - * @endcode + * @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb + * A breadcrumb object returned by BreadcrumbBuilderInterface::build(). * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The current route match. * @param array $context @@ -549,9 +545,9 @@ function hook_contextual_links_plugins_alter(array &$contextual_links) { * * @ingroup menu */ -function hook_system_breadcrumb_alter(array &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) { +function hook_system_breadcrumb_alter(Drupal\Core\Breadcrumb\Breadcrumb &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) { // Add an item to the end of the breadcrumb. - $breadcrumb[] = Drupal::l(t('Text'), 'example_route_name'); + $breadcrumb->addLink(Drupal::l(t('Text'), 'example_route_name')); } /** diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml index 0a022a7..06affbb 100644 --- a/core/modules/book/book.services.yml +++ b/core/modules/book/book.services.yml @@ -26,6 +26,8 @@ services: cache_context.route.book_navigation: class: Drupal\book\Cache\BookNavigationCacheContext arguments: ['@request_stack'] + calls: + - [setContainer, ['@service_container']] tags: - { name: cache.context} diff --git a/core/modules/book/src/BookBreadcrumbBuilder.php b/core/modules/book/src/BookBreadcrumbBuilder.php index be0e63a..b1ece44 100644 --- a/core/modules/book/src/BookBreadcrumbBuilder.php +++ b/core/modules/book/src/BookBreadcrumbBuilder.php @@ -8,6 +8,7 @@ namespace Drupal\book; use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Link; @@ -72,6 +73,8 @@ public function applies(RouteMatchInterface $route_match) { */ public function build(RouteMatchInterface $route_match) { $book_nids = array(); + $breadcrumb = new Breadcrumb(); + $links = array(Link::createFromRoute($this->t('Home'), '')); $book = $route_match->getParameter('node')->book; $depth = 1; @@ -92,7 +95,9 @@ public function build(RouteMatchInterface $route_match) { $depth++; } } - return $links; + $breadcrumb->setLinks($links); + $breadcrumb->setCacheContexts(['route.book_navigation']); + return $breadcrumb; } } diff --git a/core/modules/comment/src/CommentBreadcrumbBuilder.php b/core/modules/comment/src/CommentBreadcrumbBuilder.php index 8bc2f25..8c6866f 100644 --- a/core/modules/comment/src/CommentBreadcrumbBuilder.php +++ b/core/modules/comment/src/CommentBreadcrumbBuilder.php @@ -8,6 +8,7 @@ namespace Drupal\comment; use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Link; use Drupal\Core\Routing\RouteMatchInterface; @@ -47,19 +48,21 @@ public function applies(RouteMatchInterface $route_match) { * {@inheritdoc} */ public function build(RouteMatchInterface $route_match) { - $breadcrumb = [Link::createFromRoute($this->t('Home'), '')]; + $breadcrumb = new Breadcrumb(); + $breadcrumb->setCacheContexts(['route']); + $links = [Link::createFromRoute($this->t('Home'), '')]; $entity = $route_match->getParameter('entity'); - $breadcrumb[] = new Link($entity->label(), $entity->urlInfo()); + $links[] = new Link($entity->label(), $entity->urlInfo()); if (($pid = $route_match->getParameter('pid')) && ($comment = $this->storage->load($pid))) { /** @var \Drupal\comment\CommentInterface $comment */ // Display link to parent comment. // @todo Clean-up permalink in https://www.drupal.org/node/2198041 - $breadcrumb[] = new Link($comment->getSubject(), $comment->urlInfo()); + $links[] = new Link($comment->getSubject(), $comment->urlInfo()); } - return $breadcrumb; + return $breadcrumb->setLinks($links); } } diff --git a/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php b/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php index f595ee8..d15478f 100644 --- a/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php +++ b/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php @@ -8,6 +8,7 @@ namespace Drupal\forum\Breadcrumb; use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Link; @@ -65,14 +66,17 @@ public function __construct(EntityManagerInterface $entity_manager, ConfigFactor * {@inheritdoc} */ public function build(RouteMatchInterface $route_match) { - $breadcrumb[] = Link::createFromRoute($this->t('Home'), ''); + $breadcrumb = new Breadcrumb(); + $breadcrumb->setCacheContexts(['route']); + + $links[] = Link::createFromRoute($this->t('Home'), ''); $vocabulary = $this->entityManager ->getStorage('taxonomy_vocabulary') ->load($this->config->get('vocabulary')); - $breadcrumb[] = Link::createFromRoute($vocabulary->label(), 'forum.index'); + $links[] = Link::createFromRoute($vocabulary->label(), 'forum.index'); - return $breadcrumb; + return $breadcrumb->setLinks($links); } } diff --git a/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php b/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php index 9d63772..b446f3e 100644 --- a/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php +++ b/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php @@ -7,6 +7,7 @@ namespace Drupal\forum\Breadcrumb; +use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Link; use Drupal\Core\Routing\RouteMatchInterface; @@ -27,6 +28,7 @@ public function applies(RouteMatchInterface $route_match) { */ public function build(RouteMatchInterface $route_match) { $breadcrumb = parent::build($route_match); + $breadcrumb->addCacheContexts(['route']); // Add all parent forums to breadcrumbs. $term_id = $route_match->getParameter('taxonomy_term')->id(); @@ -34,9 +36,9 @@ public function build(RouteMatchInterface $route_match) { if ($parents) { foreach (array_reverse($parents) as $parent) { if ($parent->id() != $term_id) { - $breadcrumb[] = Link::createFromRoute($parent->label(), 'forum.page', array( + $breadcrumb->addLink(Link::createFromRoute($parent->label(), 'forum.page', array( 'taxonomy_term' => $parent->id(), - )); + ))); } } } diff --git a/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php b/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php index 090f0ea..09923f3 100644 --- a/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php +++ b/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php @@ -29,18 +29,20 @@ public function applies(RouteMatchInterface $route_match) { */ public function build(RouteMatchInterface $route_match) { $breadcrumb = parent::build($route_match); + $breadcrumb->addCacheContexts(['route']); $parents = $this->forumManager->getParents($route_match->getParameter('node')->forum_tid); if ($parents) { $parents = array_reverse($parents); foreach ($parents as $parent) { - $breadcrumb[] = Link::createFromRoute($parent->label(), 'forum.page', + $breadcrumb->addLink(Link::createFromRoute($parent->label(), 'forum.page', array( 'taxonomy_term' => $parent->id(), ) - ); + )); } } + return $breadcrumb; } diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php index da7ff11..528aceb 100644 --- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php +++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php @@ -128,7 +128,7 @@ public function testBuild() { ); // And finally, the test. - $this->assertEquals($expected, $breadcrumb_builder->build($route_match)); + $this->assertEquals($expected, $breadcrumb_builder->build($route_match)->getLinks()); } } diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php index 95c670f..b607b48 100644 --- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php +++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Link; use Drupal\Tests\UnitTestCase; use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\DependencyInjection\Container; /** * @coversDefaultClass \Drupal\forum\Breadcrumb\ForumListingBreadcrumbBuilder @@ -171,6 +172,13 @@ public function testBuild() { ) ); + $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') + ->disableOriginalConstructor() + ->getMock(); + $container = new Container(); + $container->set('cache_contexts_manager', $cache_contexts_manager); + \Drupal::setContainer($container); + // Add a translation manager for t(). $translation_manager = $this->getStringTranslationStub(); $breadcrumb_builder->setStringTranslation($translation_manager); @@ -197,7 +205,7 @@ public function testBuild() { Link::createFromRoute('Fora_is_the_plural_of_forum', 'forum.index'), Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)), ); - $this->assertEquals($expected1, $breadcrumb_builder->build($route_match)); + $this->assertEquals($expected1, $breadcrumb_builder->build($route_match)->getLinks()); // Second test. $expected2 = array( @@ -206,7 +214,7 @@ public function testBuild() { Link::createFromRoute('Something else', 'forum.page', array('taxonomy_term' => 2)), Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)), ); - $this->assertEquals($expected2, $breadcrumb_builder->build($route_match)); + $this->assertEquals($expected2, $breadcrumb_builder->build($route_match)->getLinks()); } } diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php index ec5dec0..2b94b93 100644 --- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php +++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php @@ -9,7 +9,7 @@ use Drupal\Core\Link; use Drupal\Tests\UnitTestCase; -use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\DependencyInjection\Container; /** * @coversDefaultClass \Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder @@ -179,6 +179,13 @@ public function testBuild() { ) ); + $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') + ->disableOriginalConstructor() + ->getMock(); + $container = new Container(); + $container->set('cache_contexts_manager', $cache_contexts_manager); + \Drupal::setContainer($container); + // Add a translation manager for t(). $translation_manager = $this->getStringTranslationStub(); $property = new \ReflectionProperty('Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder', 'stringTranslation'); @@ -203,7 +210,7 @@ public function testBuild() { Link::createFromRoute('Forums', 'forum.index'), Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)), ); - $this->assertEquals($expected1, $breadcrumb_builder->build($route_match)); + $this->assertEquals($expected1, $breadcrumb_builder->build($route_match)->getLinks()); // Second test. $expected2 = array( @@ -212,7 +219,7 @@ public function testBuild() { Link::createFromRoute('Something else', 'forum.page', array('taxonomy_term' => 2)), Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)), ); - $this->assertEquals($expected2, $breadcrumb_builder->build($route_match)); + $this->assertEquals($expected2, $breadcrumb_builder->build($route_match)->getLinks()); } } diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module index 0c097ba..3872aa1 100644 --- a/core/modules/menu_ui/menu_ui.module +++ b/core/modules/menu_ui/menu_ui.module @@ -486,14 +486,14 @@ function menu_ui_preprocess_block(&$variables) { /** * Implements hook_system_breadcrumb_alter(). */ -function menu_ui_system_breadcrumb_alter(array &$breadcrumb, RouteMatchInterface $route_match, array $context) { +function menu_ui_system_breadcrumb_alter(&$breadcrumb, RouteMatchInterface $route_match, array $context) { // Custom breadcrumb behavior for editing menu links, we append a link to // the menu in which the link is found. if (($route_match->getRouteName() == 'menu_ui.link_edit') && $menu_link = $route_match->getParameter('menu_link_plugin')) { if (($menu_link instanceof MenuLinkInterface)) { // Add a link to the menu admin screen. $menu = Menu::load($menu_link->getMenuName()); - $breadcrumb[] = Link::createFromRoute($menu->label(), 'entity.menu.edit_form', array('menu' => $menu->id())); + $breadcrumb->addLink(Link::createFromRoute($menu->label(), 'entity.menu.edit_form', array('menu' => $menu->id()))); } } } diff --git a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php index d8dec6a..bcc142d 100644 --- a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php +++ b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php @@ -77,6 +77,7 @@ function testPageCacheTags() { 'route.menu_active_trails:tools', 'theme', 'timezone', + 'url.path', 'user.permissions', // The cache contexts associated with the (in)accessible menu links are // bubbled. diff --git a/core/modules/system/src/PathBasedBreadcrumbBuilder.php b/core/modules/system/src/PathBasedBreadcrumbBuilder.php index c6e51af..2ee39b4 100644 --- a/core/modules/system/src/PathBasedBreadcrumbBuilder.php +++ b/core/modules/system/src/PathBasedBreadcrumbBuilder.php @@ -9,7 +9,9 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\TitleResolverInterface; use Drupal\Core\Link; @@ -125,6 +127,7 @@ public function applies(RouteMatchInterface $route_match) { * {@inheritdoc} */ public function build(RouteMatchInterface $route_match) { + $breadcrumb = new Breadcrumb(); $links = array(); // General path-based breadcrumbs. Use the actual request path, prior to @@ -139,17 +142,21 @@ public function build(RouteMatchInterface $route_match) { // /user is just a redirect, so skip it. // @todo Find a better way to deal with /user. $exclude['/user'] = TRUE; + // Because this breadcrumb builder is entirely path-based, vary by the 'url.path' + // cache context. + $breadcrumb->setCacheContexts(['url.path']); while (count($path_elements) > 1) { array_pop($path_elements); // Copy the path elements for up-casting. $route_request = $this->getRequestForPath('/' . implode('/', $path_elements), $exclude); if ($route_request) { $route_match = RouteMatch::createFromRequest($route_request); - $access = $this->accessManager->check($route_match, $this->currentUser); - if ($access) { + $access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE); + // The set of breadcrumb links depends on the access result, so merge + // the access result's cacheability metadata. + $breadcrumb = $breadcrumb->merge(CacheableMetadata::createFromObject($access)); + if ($access->isAllowed()) { $title = $this->titleResolver->getTitle($route_request, $route_match->getRouteObject()); - } - if ($access) { if (!isset($title)) { // Fallback to using the raw path component as the title if the // route is missing a _title or _title_callback attribute. @@ -165,7 +172,8 @@ public function build(RouteMatchInterface $route_match) { // Add the Home link, except for the front page. $links[] = Link::createFromRoute($this->t('Home'), ''); } - return array_reverse($links); + + return $breadcrumb->setLinks(array_reverse($links)); } /** diff --git a/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php b/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php index c7629f0..40da616 100644 --- a/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php +++ b/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php @@ -77,20 +77,13 @@ public function build() { $breadcrumb = $this->breadcrumbManager->build($this->routeMatch); if (!empty($breadcrumb)) { // $breadcrumb is expected to be an array of rendered breadcrumb links. - return array( + $build = [ '#theme' => 'breadcrumb', - '#links' => $breadcrumb, - ); + '#links' => $breadcrumb->getLinks(), + ]; + $breadcrumb->applyTo($build); + return $build; } } - /** - * {@inheritdoc} - * - * @todo Make cacheable in https://www.drupal.org/node/2483183 - */ - public function getCacheMaxAge() { - return 0; - } - } diff --git a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php index 0a00029..052f9f6 100644 --- a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php +++ b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php @@ -8,11 +8,12 @@ namespace Drupal\Tests\system\Unit\Breadcrumbs; use Drupal\Core\Link; -use Drupal\Core\Access\AccessResult; +use Drupal\Core\Access\AccessResultAllowed; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\Url; use Drupal\Core\Utility\LinkGeneratorInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\system\PathBasedBreadcrumbBuilder; use Drupal\Tests\UnitTestCase; use Symfony\Cmf\Component\Routing\RouteObjectInterface; @@ -130,7 +131,7 @@ public function testBuildOnFrontpage() { ->will($this->returnValue('/')); $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); - $this->assertEquals(array(), $links); + $this->assertEquals([], $links->getLinks()); } /** @@ -144,7 +145,7 @@ public function testBuildWithOnePathElement() { ->will($this->returnValue('/example')); $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); - $this->assertEquals(array(0 => new Link('Home', new Url(''))), $links); + $this->assertEquals([0 => new Link('Home', new Url(''))], $links->getLinks()); } /** @@ -176,7 +177,7 @@ public function testBuildWithTwoPathElements() { $this->setupAccessManagerToAllow(); $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); - $this->assertEquals(array(0 => new Link('Home', new Url('')), 1 => new Link('Example', new Url('example'))), $links); + $this->assertEquals([0 => new Link('Home', new Url('')), 1 => new Link('Example', new Url('example'))], $links->getLinks()); } /** @@ -216,11 +217,11 @@ public function testBuildWithThreePathElements() { $this->setupAccessManagerToAllow(); $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); - $this->assertEquals(array( + $this->assertEquals([ new Link('Home', new Url('')), new Link('Example', new Url('example')), new Link('Bar', new Url('example_bar')), - ), $links); + ], $links->getLinks()); } /** @@ -244,7 +245,7 @@ public function testBuildWithException($exception_class, $exception_argument) { $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); // No path matched, though at least the frontpage is displayed. - $this->assertEquals(array(0 => new Link('Home', new Url(''))), $links); + $this->assertEquals([0 => new Link('Home', new Url(''))], $links->getLinks()); } /** @@ -285,7 +286,7 @@ public function testBuildWithNonProcessedPath() { $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); // No path matched, though at least the frontpage is displayed. - $this->assertEquals(array(0 => new Link('Home', new Url(''))), $links); + $this->assertEquals([0 => new Link('Home', new Url(''))], $links->getLinks()); } /** @@ -330,7 +331,7 @@ public function testBuildWithUserPath() { ->will($this->returnValue('Admin')); $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); - $this->assertEquals(array(0 => new Link('Home', new Url('')), 1 => new Link('Admin', new Url('user_page'))), $links); + $this->assertEquals([0 => new Link('Home', new Url('')), 1 => new Link('Admin', new Url('user_page'))], $links->getLinks()); } /** @@ -339,7 +340,7 @@ public function testBuildWithUserPath() { public function setupAccessManagerToAllow() { $this->accessManager->expects($this->any()) ->method('check') - ->willReturn(TRUE); + ->willReturn(new AccessResultAllowed()); } protected function setupStubPathProcessor() { diff --git a/core/modules/taxonomy/src/TermBreadcrumbBuilder.php b/core/modules/taxonomy/src/TermBreadcrumbBuilder.php index c2e47a1..8f4fc26 100644 --- a/core/modules/taxonomy/src/TermBreadcrumbBuilder.php +++ b/core/modules/taxonomy/src/TermBreadcrumbBuilder.php @@ -8,6 +8,7 @@ namespace Drupal\taxonomy; use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Link; use Drupal\Core\Routing\RouteMatchInterface; @@ -67,9 +68,14 @@ public function build(RouteMatchInterface $route_match) { $breadcrumb[] = Link::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id())); } $breadcrumb[] = Link::createFromRoute($this->t('Home'), ''); - $breadcrumb = array_reverse($breadcrumb); + $links = array_reverse($breadcrumb); - return $breadcrumb; + $breadcrumb = new Breadcrumb(); + // This breadcrumb builder is based on a route parameter, and hence it + // depends on the 'route' cache context. + $breadcrumb->setCacheContexts(['route']); + + return $breadcrumb->setLinks($links); } } diff --git a/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php b/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php index a2cbbf0..1e21ad9 100644 --- a/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php @@ -7,8 +7,10 @@ namespace Drupal\Tests\Core\Breadcrumb; +use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Breadcrumb\BreadcrumbManager; use Drupal\Tests\UnitTestCase; +use PHPUnit_Framework_Assert as Assert; /** * @coversDefaultClass \Drupal\Core\Breadcrumb\BreadcrumbManager @@ -17,6 +19,13 @@ class BreadcrumbManagerTest extends UnitTestCase { /** + * The breadcrumb object. + * + * @var \Drupal\Core\Breadcrumb\Breadcrumb + */ + protected $breadcrumb; + + /** * The tested breadcrumb manager. * * @var \Drupal\Core\Breadcrumb\BreadcrumbManager @@ -36,6 +45,7 @@ class BreadcrumbManagerTest extends UnitTestCase { protected function setUp() { $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); $this->breadcrumbManager = new BreadcrumbManager($this->moduleHandler); + $this->breadcrumb = new Breadcrumb(); } /** @@ -43,7 +53,7 @@ protected function setUp() { */ public function testBuildWithoutBuilder() { $result = $this->breadcrumbManager->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface')); - $this->assertEquals(array(), $result); + $this->assertEquals(array(), $result->getLinks()); } /** @@ -51,7 +61,8 @@ public function testBuildWithoutBuilder() { */ public function testBuildWithSingleBuilder() { $builder = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface'); - $breadcrumb = array('Test'); + $links = array('Test'); + $this->breadcrumb->setLinks($links); $builder->expects($this->once()) ->method('applies') @@ -59,17 +70,24 @@ public function testBuildWithSingleBuilder() { $builder->expects($this->once()) ->method('build') - ->will($this->returnValue($breadcrumb)); + ->willReturn($this->breadcrumb); $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); $this->moduleHandler->expects($this->once()) ->method('alter') - ->with('system_breadcrumb', $breadcrumb, $route_match, array('builder' => $builder)); + ->willReturnCallback(function($type, $data, $context1, $context2) use ($route_match, $links, $builder) { + Assert::assertEquals($type, 'system_breadcrumb', 'SingleTest: Module handler expected alter call to system_breadcrumb'); + $extracted_links = $data->getLinks(); + Assert::assertEquals($extracted_links, $links, 'SingleTest: Module handler was passed the correct link'); + Assert::assertEquals($context1, $route_match, 'SingleTest: Module handler was passed route_match.'); + Assert::assertEquals($context2, array('builder' => $builder), 'SingleTest: Module handler received the builder context'); + } + ); $this->breadcrumbManager->addBuilder($builder, 0); $result = $this->breadcrumbManager->build($route_match); - $this->assertEquals($breadcrumb, $result); + $this->assertEquals($links, $result->getLinks()); } /** @@ -83,25 +101,33 @@ public function testBuildWithMultipleApplyingBuilders() { ->method('build'); $builder2 = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface'); - $breadcrumb2 = array('Test2'); + $links2 = array('Test2'); + $this->breadcrumb->setLinks($links2); $builder2->expects($this->once()) ->method('applies') ->will($this->returnValue(TRUE)); $builder2->expects($this->once()) ->method('build') - ->will($this->returnValue($breadcrumb2)); + ->willReturn($this->breadcrumb); $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); $this->moduleHandler->expects($this->once()) ->method('alter') - ->with('system_breadcrumb', $breadcrumb2, $route_match, array('builder' => $builder2)); + ->willReturnCallback(function($type, $data, $context1, $context2) use ($links2, $route_match, $builder2) { + Assert::assertEquals($type, 'system_breadcrumb', 'MultipleApplying: Module handler expected alter call to system_breadcrumb'); + $extracted_links = $data->getLinks(); + Assert::assertEquals($extracted_links, $links2, 'MultipleApplying: Module handler was passed the correct link'); + Assert::assertEquals($context1, $route_match, 'MultipleApplying: Module handler was passed route_match.'); + Assert::assertEquals($context2, array('builder' => $builder2), 'MultipleApplying: Module handler received the builder context'); + } + ); $this->breadcrumbManager->addBuilder($builder1, 0); $this->breadcrumbManager->addBuilder($builder2, 10); $result = $this->breadcrumbManager->build($route_match); - $this->assertEquals($breadcrumb2, $result); + $this->assertEquals($links2, $result->getLinks()); } /** @@ -116,25 +142,33 @@ public function testBuildWithOneNotApplyingBuilders() { ->method('build'); $builder2 = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface'); - $breadcrumb2 = array('Test2'); + $links2 = array('Test2'); + $this->breadcrumb->setLinks($links2); $builder2->expects($this->once()) ->method('applies') ->will($this->returnValue(TRUE)); $builder2->expects($this->once()) ->method('build') - ->will($this->returnValue($breadcrumb2)); + ->willReturn($this->breadcrumb); $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); $this->moduleHandler->expects($this->once()) ->method('alter') - ->with('system_breadcrumb', $breadcrumb2, $route_match, array('builder' => $builder2)); + ->willReturnCallback(function($type, $data, $context1, $context2) use ($links2, $route_match, $builder2) { + Assert::assertEquals($type, 'system_breadcrumb', 'NotApplying: Module handler expected alter call to system_breadcrumb'); + $extracted_links = $data->getLinks(); + Assert::assertEquals($extracted_links, $links2, 'NotApplying: Module handler was passed the correct link'); + Assert::assertEquals($context1, $route_match, 'NotApplying: Module handler was passed route_match.'); + Assert::assertEquals($context2, array('builder' => $builder2), 'NotApplying: Module handler received the builder context'); + } + ); $this->breadcrumbManager->addBuilder($builder1, 10); $this->breadcrumbManager->addBuilder($builder2, 0); $result = $this->breadcrumbManager->build($route_match); - $this->assertEquals($breadcrumb2, $result); + $this->assertEquals($links2, $result->getLinks()); } /**