MenuLinkTree::buildItems() ignores cache metadata of disabled links

Created on 26 January 2018, over 6 years ago
Updated 21 February 2023, over 1 year ago

Problem

MenuLinkTree::buildItems ignores cacheable metadata of disabled links. This is bad for dynamically enabled links.

Example

I have created a simple menu link plugin which shows a link to the front page on any page except the front page itself. Code:

<?php

namespace Drupal\blablabla\Plugin\Menu;

use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
use Drupal\Core\Path\PathMatcherInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * A menu link for front page.
 */
class FrontpageMenuLink extends MenuLinkDefault {

  /**
   * The path matcher.
   *
   * @var \Drupal\Core\Path\PathMatcherInterface
   */
  protected $pathMatcher;

  /**
   * Constructs a new FrontpageMenuLink.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
   *   The static override storage.
   * @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
   *   The path matcher.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override, PathMatcherInterface $path_matcher) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $static_override);
    $this->pathMatcher = $path_matcher;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('menu_link.static.overrides'),
      $container->get('path.matcher')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getTitle() {
    return $this->t('Frontpage');
  }

  /**
   * {@inheritdoc}
   */
  public function getRouteName() {
    return '<front>';
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return ['url.path.is_front'];
  }

  /**
   * {@inheritdoc}
   */
  public function isEnabled()
  {
    return (parent::isEnabled() && !$this->pathMatcher->isFrontPage());
  }

}

?>

Steps to reproduce cache ignorance:

  1. Use this plugin, place a link of it into the main menu
  2. Clear cache
  3. Request the front page: No link to front page is set (OK)
  4. Request a page other than front: No link to front page is set (not OK, because wrong cache record is being fetched).
  5. Clear cache
  6. Request a page other than front: You see the link to front page (OK, because Drupal now knows about the cache context)
  7. Request the front page: No link to front page is set (OK)

Proposed solution

Collect cache metadata from links regardless whether they're disabled or not. I'm attaching a patch for this.

πŸ› Bug report
Status

Needs work

Version

9.5

Component
Menu systemΒ  β†’

Last updated about 22 hours ago

Created by

πŸ‡©πŸ‡ͺGermany mxh Offenburg

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Triggering for D10

  • Status changed to RTBC over 1 year ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request β†’ as a guide.

    Removing tests tag as they were added in #13

    The patch appears to be doing whats in the issue summary and cache the menu link sooner.

    Test coverage shows the issue.

    Looks good.

  • Status changed to Needs work over 1 year ago
  • πŸ‡¬πŸ‡§United Kingdom catch
    +++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
    @@ -215,6 +215,13 @@ protected function buildItems(array $tree, CacheableMetadata &$tree_access_cache
    +
           // Generally we only deal with visible links, but just in case.
           if (!$link->isEnabled()) {
             continue;
    @@ -235,12 +242,6 @@ protected function buildItems(array $tree, CacheableMetadata &$tree_access_cache
    
    @@ -235,12 +242,6 @@ protected function buildItems(array $tree, CacheableMetadata &$tree_access_cache
             $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($data->access));
           }
    

    Why is it OK to only collect the $tree_access_cacheability from visible links, should this be moved out of the if?

    If it is OK, I think we need a comment explaining why.

Production build 0.71.5 2024