diff --git a/core/core.services.yml b/core/core.services.yml index e9ab1793d494accb3f288a504d29ad68f88785e4..77e4f5162127587e4f9b0d9b23f6dd78b6b5e8fd 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -927,7 +927,7 @@ services: arguments: ['@plugin.manager.menu.link'] menu.tree_storage: class: Drupal\Core\Menu\MenuTreeStorage - arguments: ['@database', '@cache.menu', '@cache_tags.invalidator', 'menu_tree'] + arguments: ['@database', '@cache.menu', '@cache_tags.invalidator', 'menu_tree', '@router.route_provider'] public: false # Private to plugin.manager.menu.link and menu.link_tree tags: - { name: backend_overridable } diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php index ad35c5a92131231db54582e845398f6a03390d3d..17d0acc31c8b1669434007ca30c3453263182af7 100644 --- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php +++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php @@ -11,6 +11,8 @@ use Drupal\Core\Database\DatabaseException; use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Database\Statement\FetchAs; +use Drupal\Core\Routing\RouteProviderInterface; +use Symfony\Component\Routing\Exception\RouteNotFoundException; // cspell:ignore mlid @@ -97,14 +99,38 @@ class MenuTreeStorage implements MenuTreeStorageInterface { * The cache tags invalidator. * @param string $table * A database table name to store configuration data in. + * @param \Drupal\Core\Routing\RouteProviderInterface|array|null $routeProvider + * (optional) The route provider to use for loading routes. + * Passing an array instead is deprecated as of Drupal 11.5.0, and used to + * be for passing options. Use the $options parameter instead. * @param array $options - * (optional) Any additional database connection options to use in queries. + * (optional) Additional database connection options to use in queries. */ - public function __construct(Connection $connection, CacheBackendInterface $menu_cache_backend, CacheTagsInvalidatorInterface $cache_tags_invalidator, $table, array $options = []) { + public function __construct( + Connection $connection, + CacheBackendInterface $menu_cache_backend, + CacheTagsInvalidatorInterface $cache_tags_invalidator, + string $table, + protected RouteProviderInterface|array|null $routeProvider = NULL, + array $options = [], + ) { $this->connection = $connection; $this->menuCacheBackend = $menu_cache_backend; $this->cacheTagsInvalidator = $cache_tags_invalidator; $this->table = $table; + + // Handle backwards compatibility. + if (!$this->routeProvider instanceof RouteProviderInterface) { + if (is_array($this->routeProvider)) { + $options = $this->routeProvider; + } + $this->routeProvider = \Drupal::service('router.route_provider'); + @trigger_error( + 'Calling ' . __METHOD__ . '() without the $route_provider argument is deprecated in drupal:11.5.0 and will be required in drupal:13.0.0. See https://www.drupal.org/node/3364323', + E_USER_DEPRECATED + ); + } + $this->options = $options; } @@ -689,17 +715,33 @@ public function loadByProperties(array $properties) { * {@inheritdoc} */ public function loadByRoute($route_name, array $route_parameters = [], $menu_name = NULL) { + // Also query without any default route parameters as they may not be + // present in the computed route_param_key. + $route_parameters_without_defaults = $route_parameters; + try { + $route = $this->routeProvider->getRouteByName($route_name); + foreach (array_keys($route_parameters_without_defaults) as $param) { + if ($route->hasDefault($param)) { + unset($route_parameters_without_defaults[$param]); + } + } + } + catch (RouteNotFoundException) { + // No such route, we cannot remove defaults from the route parameters. + } // Sort the route parameters so that the query string will be the same. asort($route_parameters); + asort($route_parameters_without_defaults); // Since this will be urlencoded, it's safe to store and match against a // text field. // @todo Standardize an efficient way to load by route name and parameters // in place of system path. https://www.drupal.org/node/2302139 $param_key = $route_parameters ? UrlHelper::buildQuery($route_parameters) : ''; + $param_key_without_defaults = $route_parameters_without_defaults ? UrlHelper::buildQuery($route_parameters_without_defaults) : ''; $query = $this->connection->select($this->table, NULL, $this->options); $query->fields($this->table, $this->definitionFields()); - $query->condition('route_name', $route_name); - $query->condition('route_param_key', $param_key); + $query->condition('route_name', $route_name) + ->condition('route_param_key', [$param_key, $param_key_without_defaults], 'IN'); if ($menu_name) { $query->condition('menu_name', $menu_name); } @@ -707,6 +749,7 @@ public function loadByRoute($route_name, array $route_parameters = [], $menu_nam $query->orderBy('depth'); $query->orderBy('weight'); $query->orderBy('id'); + $query->orderBy('route_param_key'); $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', FetchAs::Associative); foreach ($loaded as $id => $link) { $loaded[$id] = $this->prepareLink($link); diff --git a/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php b/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php index 0e72154760441a17d19449d76ce60270c6ead3c5..b9e689305137006125599108285faef23ff35171 100644 --- a/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php +++ b/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php @@ -43,23 +43,8 @@ public function __construct( */ public function access(RouteMatchInterface $route_match, AccountInterface $account): AccessResultInterface { $parameters = $route_match->getParameters()->all(); - $route = $route_match->getRouteObject(); - // Load links in the 'admin' menu matching this route. First, try to find - // the menu link using all specified parameters. + // Load links in the 'admin' menu matching this route. $links = $this->menuLinkManager->loadLinksByRoute($route_match->getRouteName(), $parameters, 'admin'); - // If the menu link was not found, try finding it without the parameters - // that match the route defaults. Depending on whether the parameter is - // specified in the menu item with a value matching the default, or not - // specified at all, will change how it is stored in the menu_tree table. In - // both cases the route match parameters will always include the default - // parameters. This fallback method of finding the menu item is needed so - // that menu items will work in either case. - // @todo Remove this fallback in https://drupal.org/i/3359511. - if (empty($links)) { - - $parameters_without_defaults = array_filter($parameters, fn ($key) => !$route->hasDefault($key) || $route->getDefault($key) !== $parameters[$key], ARRAY_FILTER_USE_KEY); - $links = $this->menuLinkManager->loadLinksByRoute($route_match->getRouteName(), $parameters_without_defaults, 'admin'); - } if (empty($links)) { // If we did not find a link then we have no opinion on access. return AccessResult::neutral(); diff --git a/core/modules/workspaces/src/WorkspacesMenuTreeStorage.php b/core/modules/workspaces/src/WorkspacesMenuTreeStorage.php index f6af97303538195d553f8bcfb14086f854700e96..8b52f10d2a814630b1e70df88c2b77fe26671483 100644 --- a/core/modules/workspaces/src/WorkspacesMenuTreeStorage.php +++ b/core/modules/workspaces/src/WorkspacesMenuTreeStorage.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Menu\MenuTreeStorage as CoreMenuTreeStorage; +use Drupal\Core\Routing\RouteProviderInterface; /** * Overrides the default menu storage to provide workspace-specific menu links. @@ -33,6 +34,8 @@ class WorkspacesMenuTreeStorage extends CoreMenuTreeStorage { * The cache tags invalidator. * @param string $table * A database table name to store configuration data in. + * @param \Drupal\Core\Routing\RouteProviderInterface|array|null $routeProvider + * Route provider. * @param array $options * (optional) Any additional database connection options to use in queries. */ @@ -44,9 +47,10 @@ public function __construct( CacheBackendInterface $menu_cache_backend, CacheTagsInvalidatorInterface $cache_tags_invalidator, string $table, + protected RouteProviderInterface|array|null $routeProvider = NULL, array $options = [], ) { - parent::__construct($connection, $menu_cache_backend, $cache_tags_invalidator, $table, $options); + parent::__construct($connection, $menu_cache_backend, $cache_tags_invalidator, $table, $this->routeProvider, $options); } /** diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml index 925eb3cd90f1162f5392cff9c6ccedf94a47293e..282c5330a9d63adf811eb0fbba0bb77bee1cd8b7 100644 --- a/core/modules/workspaces/workspaces.services.yml +++ b/core/modules/workspaces/workspaces.services.yml @@ -73,7 +73,7 @@ services: workspaces.menu.tree_storage: decorates: menu.tree_storage class: Drupal\workspaces\WorkspacesMenuTreeStorage - arguments: ['@workspaces.manager', '@workspaces.tracker', '@entity_type.manager', '@database', '@cache.menu', '@cache_tags.invalidator', 'menu_tree'] + arguments: ['@workspaces.manager', '@workspaces.tracker', '@entity_type.manager', '@database', '@cache.menu', '@cache_tags.invalidator', 'menu_tree', '@router.route_provider'] public: false decoration_priority: 50 tags: