diff --git a/menu_position.services.yml b/menu_position.services.yml index 7f5bb9d7e7ff259276d22dff252e6328036fce26..ae3bc04a07ad18923fd218bcfc2334fa0880dc4c 100644 --- a/menu_position.services.yml +++ b/menu_position.services.yml @@ -2,3 +2,12 @@ services: plugin.manager.menu_position_condition_plugin.processor: class: Drupal\menu_position\Plugin\MenuPositionConditionPluginManager parent: default_plugin_manager + + menu_position.menu.active_trail: + class: Drupal\menu_position\Menu\MenuPositionActiveTrail + public: false + decorates: menu.active_trail + decoration_priority: 9 + arguments: [ '@menu_position.menu.active_trail.inner', '@plugin.manager.menu.link', '@current_route_match', '@cache.menu', '@lock', '@entity_type.manager', '@config.factory' ] + tags: + - { name: needs_destruction } diff --git a/src/Menu/MenuPositionActiveTrail.php b/src/Menu/MenuPositionActiveTrail.php index a7344ed09d708da5ae429412c65fa18c32c8a846..6353f7160017be602d8517cfe5168ac000ecd831 100644 --- a/src/Menu/MenuPositionActiveTrail.php +++ b/src/Menu/MenuPositionActiveTrail.php @@ -3,59 +3,156 @@ namespace Drupal\menu_position\Menu; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Cache\CacheCollector; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Lock\LockBackendInterface; -use Drupal\Core\Menu\MenuActiveTrail; +use Drupal\Core\Menu\MenuActiveTrailInterface; use Drupal\Core\Menu\MenuLinkManagerInterface; use Drupal\Core\Routing\RouteMatchInterface; /** * Menu Position active trail. + * + * Decorates the MenuActiveTrail class. */ -class MenuPositionActiveTrail extends MenuActiveTrail { +class MenuPositionActiveTrail extends CacheCollector implements MenuActiveTrailInterface { + + use DependencySerializationTrait; /** * The entity type manager. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface + * @var EntityTypeManagerInterface */ protected $entityTypeManager; /** * Menu position settings. * - * @var \Drupal\Core\Config\ImmutableConfig + * @var ImmutableConfig */ protected $settings; + /** + * The menu link plugin manager. + * + * @var MenuLinkManagerInterface + */ + protected $menuLinkManager; + + /** + * The route match object for the current page. + * + * @var RouteMatchInterface + */ + protected $routeMatch; + + /** + * The decorated MenuActiveTrail service. + * + * @var MenuActiveTrailInterface + */ + protected $inner; + /** * Constructs a \Drupal\Core\Menu\MenuActiveTrail object. * - * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager + * @param MenuActiveTrailInterface $menu_active_trail + * The menu link plugin manager. + * @param MenuLinkManagerInterface $menu_link_manager * The menu link plugin manager. - * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * @param RouteMatchInterface $route_match * A route match object for finding the active link. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache + * @param CacheBackendInterface $cache * The cache backend service. - * @param \Drupal\Core\Lock\LockBackendInterface $lock + * @param LockBackendInterface $lock * The lock backend service. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * @param EntityTypeManagerInterface $entity_type_manager * The entity type manager. - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * @param ConfigFactoryInterface $config_factory * The config factory service. */ public function __construct( + MenuActiveTrailInterface $menu_active_trail, MenuLinkManagerInterface $menu_link_manager, RouteMatchInterface $route_match, CacheBackendInterface $cache, LockBackendInterface $lock, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory) { - - parent::__construct($menu_link_manager, $route_match, $cache, $lock); + $this->inner = $menu_active_trail; + $this->menuLinkManager = $menu_link_manager; + $this->routeMatch = $route_match; $this->entityTypeManager = $entity_type_manager; $this->settings = $config_factory->get('menu_position.settings'); + parent::__construct(NULL, $cache, $lock); + } + + /** + * {@inheritdoc} + * + * @see ::getActiveTrailIds() + */ + protected function getCid() { + if (!isset($this->cid)) { + $route_parameters = $this->routeMatch->getRawParameters()->all(); + ksort($route_parameters); + $this->cid = 'active-trail:route:' . $this->routeMatch->getRouteName() . ':route_parameters:' . serialize($route_parameters); + } + + return $this->cid; + } + + /** + * {@inheritdoc} + * + * @see ::getActiveTrailIds() + */ + protected function resolveCacheMiss($menu_name) { + $this->storage[$menu_name] = $this->doGetActiveTrailIds($menu_name); + $this->tags[] = 'config:system.menu.' . $menu_name; + $this->persist($menu_name); + + return $this->storage[$menu_name]; + } + + /** + * {@inheritdoc} + * + * This implementation caches all active trail IDs per route match for *all* + * menus whose active trails are calculated on that page. This ensures 1 cache + * get for all active trails per page load, rather than N. + * + * It uses the cache collector pattern to do this. + * + * @see ::get() + * @see \Drupal\Core\Cache\CacheCollectorInterface + * @see CacheCollector + */ + public function getActiveTrailIds($menu_name) { + return $this->get($menu_name); + } + + /** + * Helper method for ::getActiveTrailIds(). + */ + protected function doGetActiveTrailIds($menu_name) { + // Parent ids; used both as key and value to ensure uniqueness. + // We always want all the top-level links with parent == ''. + $active_trail = ['' => '']; + + // If a link in the given menu indeed matches the route, then use it to + // complete the active trail. + if ($active_link = $this->getActiveLink($menu_name)) { + if ($parents = $this->menuLinkManager->getParentIds($active_link->getPluginId())) { + $active_trail = $parents + $active_trail; + } + } + + return $active_trail; } /** @@ -104,7 +201,6 @@ class MenuPositionActiveTrail extends MenuActiveTrail { } // Default implementation takes here. - return parent::getActiveLink($menu_name); + return $this->inner->getActiveLink($menu_name); } - } diff --git a/src/MenuPositionServiceProvider.php b/src/MenuPositionServiceProvider.php deleted file mode 100644 index 2ecebb28adce83be857d4bfa0188d4c89bf7d79d..0000000000000000000000000000000000000000 --- a/src/MenuPositionServiceProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php - -namespace Drupal\menu_position; - -use Symfony\Component\DependencyInjection\Reference; -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\DependencyInjection\ServiceProviderBase; - -/** - * Menu Position service provider. - */ -class MenuPositionServiceProvider extends ServiceProviderBase { - - /** - * {@inheritdoc} - */ - public function alter(ContainerBuilder $container) { - // Override the menu active trail with a new class. - $definition = $container->getDefinition('menu.active_trail'); - $definition->setClass('Drupal\menu_position\Menu\MenuPositionActiveTrail'); - $definition->addArgument(new Reference('entity_type.manager')); - $definition->addArgument(new Reference('config.factory')); - } - -}