Issue #3482691 by james.williams, arunkumark, kristiaanvandeneynde, smustgrave: BreadcrumbManager ignores cacheability when no builders apply

(cherry picked from commit 78bbe4db98)
merge-requests/10722/head
Lee Rowlands 2024-12-24 06:51:01 +10:00
parent 91e01793cb
commit a8556f6f3c
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
5 changed files with 89 additions and 2 deletions

View File

@ -83,16 +83,18 @@ class BreadcrumbManager implements ChainBreadcrumbBuilderInterface {
}
$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);

View File

@ -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'

View File

@ -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'));
}
}
}

View File

@ -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);
}
}

View File

@ -388,6 +388,15 @@ class BreadcrumbTest extends BrowserTestBase {
$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);
}
/**