From a8556f6f3c24a71180e55c8f55e15e61741be81d Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Tue, 24 Dec 2024 06:51:01 +1000 Subject: [PATCH] Issue #3482691 by james.williams, arunkumark, kristiaanvandeneynde, smustgrave: BreadcrumbManager ignores cacheability when no builders apply (cherry picked from commit 78bbe4db986fb8ce21a892eec8c971fb92dd73db) --- .../Core/Breadcrumb/BreadcrumbManager.php | 6 ++- .../modules/menu_test/menu_test.routing.yml | 8 ++++ .../menu_test/src/MenuTestServiceProvider.php | 28 +++++++++++++ .../SkippablePathBasedBreadcrumbBuilder.php | 40 +++++++++++++++++++ .../src/Functional/Menu/BreadcrumbTest.php | 9 +++++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 core/modules/system/tests/modules/menu_test/src/MenuTestServiceProvider.php create mode 100644 core/modules/system/tests/modules/menu_test/src/SkippablePathBasedBreadcrumbBuilder.php diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php index 1c745ed6a262..0a75387dd1d8 100644 --- a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php +++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php @@ -83,16 +83,18 @@ public function build(RouteMatchInterface $route_match) { } $breadcrumb = $builder->build($route_match); - if ($breadcrumb instanceof Breadcrumb) { $context['builder'] = $builder; - $breadcrumb->addCacheableDependency($cacheable_metadata); break; } else { throw new \UnexpectedValueException('Invalid breadcrumb returned by ' . get_class($builder) . '::build().'); } } + + // Ensure all collected cacheability is applied. + $breadcrumb->addCacheableDependency($cacheable_metadata); + // Allow modules to alter the breadcrumb. $this->moduleHandler->alter('system_breadcrumb', $breadcrumb, $route_match, $context); diff --git a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml index 4f3c9e28b1c5..f427fe5fe87e 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml +++ b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml @@ -577,3 +577,11 @@ menu_test.breadcrumb3: _title: 'Normal title' requirements: _access: 'TRUE' + +menu_test.skippable-breadcrumb: + path: '/menu-test/skippable-breadcrumb' + defaults: + _controller: '\Drupal\menu_test\Controller\MenuTestController::menuTestCallback' + _title: 'Normal title' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/menu_test/src/MenuTestServiceProvider.php b/core/modules/system/tests/modules/menu_test/src/MenuTestServiceProvider.php new file mode 100644 index 000000000000..f6aad34ecf18 --- /dev/null +++ b/core/modules/system/tests/modules/menu_test/src/MenuTestServiceProvider.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\menu_test; + +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\DependencyInjection\ServiceModifierInterface; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Decorate core's default path-based breadcrumb builder when it is available. + */ +class MenuTestServiceProvider implements ServiceModifierInterface { + + /** + * {@inheritdoc} + */ + public function alter(ContainerBuilder $container): void { + if ($container->has('system.breadcrumb.default')) { + $container->register('menu_test.breadcrumb.default', SkippablePathBasedBreadcrumbBuilder::class) + ->setDecoratedService('system.breadcrumb.default') + ->addArgument(new Reference('menu_test.breadcrumb.default.inner')) + ->addArgument(new Reference('request_stack')); + } + } + +} diff --git a/core/modules/system/tests/modules/menu_test/src/SkippablePathBasedBreadcrumbBuilder.php b/core/modules/system/tests/modules/menu_test/src/SkippablePathBasedBreadcrumbBuilder.php new file mode 100644 index 000000000000..fc075042ad6e --- /dev/null +++ b/core/modules/system/tests/modules/menu_test/src/SkippablePathBasedBreadcrumbBuilder.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\menu_test; + +use Drupal\Core\Breadcrumb\Breadcrumb; +use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Routing\RouteMatchInterface; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * A path-based breadcrumb builder can be skipped from applying. + */ +class SkippablePathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface { + + public function __construct( + protected BreadcrumbBuilderInterface $pathBasedBreadcrumbBuilder, + protected RequestStack $requestStack, + ) {} + + /** + * {@inheritdoc} + */ + public function applies(RouteMatchInterface $route_match, ?CacheableMetadata $cacheable_metadata = NULL): bool { + $query_arg = 'menu_test_skip_breadcrumbs'; + $cacheable_metadata?->addCacheContexts(['url.query_args:' . $query_arg]); + // Apply unless the query argument is present. + return !$this->requestStack->getCurrentRequest()->query->has($query_arg); + } + + /** + * {@inheritdoc} + */ + public function build(RouteMatchInterface $route_match): Breadcrumb { + return $this->pathBasedBreadcrumbBuilder->build($route_match); + } + +} diff --git a/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php b/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php index ce5ebd448eef..e450a0de6e63 100644 --- a/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php +++ b/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php @@ -388,6 +388,15 @@ public function testBreadCrumbs(): void { $this->drupalGet('menu-test/breadcrumb1/breadcrumb2/breadcrumb3'); $this->assertSession()->responseContains('<script>alert(12);</script>'); $this->assertSession()->assertEscaped('<script>alert(123);</script>'); + + // Assert that the breadcrumb cacheability is respected after not applying. + $this->assertBreadcrumb(Url::fromRoute('menu_test.skippable-breadcrumb', [], [ + 'query' => [ + 'menu_test_skip_breadcrumbs' => 'yes', + ], + ]), []); + $trail = $home + ['menu-test' => 'Menu test root']; + $this->assertBreadcrumb(Url::fromRoute('menu_test.skippable-breadcrumb'), $trail); } /** -- GitLab