diff --git a/core/lib/Drupal/Core/Menu/MenuLinkManager.php b/core/lib/Drupal/Core/Menu/MenuLinkManager.php index 3647e07..c307ecd 100644 --- a/core/lib/Drupal/Core/Menu/MenuLinkManager.php +++ b/core/lib/Drupal/Core/Menu/MenuLinkManager.php @@ -12,9 +12,11 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\String; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\ModuleDependencyTrait; use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator; use Drupal\Core\Plugin\Discovery\YamlDiscovery; use Drupal\Core\Plugin\Factory\ContainerFactory; +use Symfony\Component\Routing\RouteCollection; /** @@ -24,6 +26,8 @@ */ class MenuLinkManager implements MenuLinkManagerInterface { + use ModuleDependencyTrait; + /** * Provides some default values for the definition of all menu link plugins. * @@ -104,7 +108,6 @@ class MenuLinkManager implements MenuLinkManagerInterface { */ protected $moduleHandler; - /** * Constructs a \Drupal\Core\Menu\MenuLinkManager object. * @@ -183,6 +186,29 @@ public function getDefinitions() { unset($definitions[$plugin_id]); } } + + // Filter out definitions with unmet module dependencies. + $this->filterByModuleDependencies($definitions); + + // Also filter by the unmet route dependencies. + $routeDiscovery = new YamlDiscovery('routing', $this->moduleHandler->getModuleDirectories()); + $route_definitions = $routeDiscovery->getDefinitions(); + foreach ($definitions as $plugin_id => $plugin_definition) { + $route_name = $plugin_definition['route_name']; + if (!empty($route_definitions[$route_name]) && !empty($route_definitions[$route_name]['requirements']) && !empty($route_definitions[$route_name]['requirements']['_module_dependencies'])) { + if ($modules = $route_definitions[$route_name]['requirements']['_module_dependencies']) { + $explode_and = $this->explodeString($modules, '+'); + foreach ($explode_and as $module) { + // If any moduleExists() call returns FALSE, remove the menu and + // move on to the next. + if (!$this->moduleHandler->moduleExists($module)) { + unset($definitions[$plugin_id]); + } + } + } + } + } + return $definitions; } diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php index 5828d33..c94356b 100644 --- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php +++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php @@ -27,6 +27,7 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInterface, CachedDiscoveryInterface { use DiscoveryCachedTrait; + use ModuleDependencyTrait; /** * Cache backend instance. @@ -239,6 +240,9 @@ protected function findDefinitions() { unset($definitions[$plugin_id]); } } + + $this->filterByModuleDependencies($definitions); + return $definitions; } diff --git a/core/lib/Drupal/Core/Plugin/ModuleDependencyTrait.php b/core/lib/Drupal/Core/Plugin/ModuleDependencyTrait.php new file mode 100644 index 0000000..8bc5266 --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/ModuleDependencyTrait.php @@ -0,0 +1,105 @@ + &$definition) { + if (isset($definition['module_dependencies'])) { + if (($this->checkModuleDependencies($definition['module_dependencies'])) == FALSE) { + unset($definitions[$plugin_id]); + } + } + } + } + + /** + * Checks whether module dependencies are satisfied. + * + * @param string $modules + * The list of required modules, separated by a '+' (AND) or ','' (OR). + * + * @return bool + * TRUE if the dependencies are satisfied, FALSE otherwise. + */ + protected function checkModuleDependencies($modules) { + $explode_and = is_array($modules) ? $modules : $this->explodeString($modules, '+'); + if (count($explode_and) > 1) { + foreach ($explode_and as $module) { + if (!$this->getModuleHandler()->moduleExists($module)) { + return FALSE; + } + } + return TRUE; + } + else { + // OR condition, exploding on ',' character. + foreach ($this->explodeString($modules, ',') as $module) { + if ($this->getModuleHandler()->moduleExists($module)) { + return TRUE; + } + } + return FALSE; + } + } + + /** + * Explodes a string based on a separator. + * + * @param string $string + * The string to explode. + * @param string $separator + * The string separator to explode with. + * + * @return array + * An array of exploded (and trimmed) values. + */ + protected function explodeString($string, $separator = ',') { + return array_filter(array_map('trim', explode($separator, $string))); + } + + /** + * Returns the module handler. + * + * @return \Drupal\Core\Extension\ModuleHandlerInterface + * The module handler. + */ + protected function getModuleHandler() { + if (!$this->moduleHandler) { + $this->moduleHandler = \Drupal::moduleHandler(); + } + return $this->moduleHandler; + } + + /** + * Sets the module handler. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * + * @return $this + */ + public function setModuleHandler(ModuleHandlerInterface $module_handler) { + $this->moduleHandler = $module_handler; + + return $this; + } + +} diff --git a/core/modules/forum/src/Access/RouteProcessor.php b/core/modules/forum/src/Access/RouteProcessor.php new file mode 100644 index 0000000..a6f0bab --- /dev/null +++ b/core/modules/forum/src/Access/RouteProcessor.php @@ -0,0 +1,61 @@ +configFactory = $config_factory; + $this->entityManager = $entity_manager; + } + + /** + * {@inheritdoc} + */ + public function processOutbound($route_name, Route $route, array &$parameters) { + if ($route_name == 'entity.taxonomy_term.canonical' && !empty($parameters['taxonomy_term'])) { + // Take over URI construction for taxonomy terms that belongs to forum. + if ($vid = $this->configFactory->get('forum.settings')->get('vocabulary')) { + if ($this->entityManager->getStorage('taxonomy_term')->load($parameters['taxonomy_term'])->getVocabularyId() == $vid) { + $route->setPath('/forum/{taxonomy_term}'); + } + } + } + } + +} diff --git a/core/modules/system/src/Tests/Menu/MenuLinkDependencyTest.php b/core/modules/system/src/Tests/Menu/MenuLinkDependencyTest.php new file mode 100644 index 0000000..f8c69c1 --- /dev/null +++ b/core/modules/system/src/Tests/Menu/MenuLinkDependencyTest.php @@ -0,0 +1,56 @@ +getDefinitions(); + $this->assertTrue(empty($definitions['menu_dependency_test.dependency']), 'A menu link with unmet dependencies is not found.'); + + \Drupal::service('module_installer')->install(['system', 'menu_ui']); + $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); + $definitions = $menu_link_manager->getDefinitions(); + $this->assertTrue(!empty($definitions['menu_dependency_test.dependency']), 'Menu link is found when the dependent module is enabled.'); + } + + /** + * Test route dependencies. + */ + public function testMenuLinkRouteDependency() { + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); + $definitions = $menu_link_manager->getDefinitions(); + $this->assertTrue(empty($definitions['menu_dependency_test.route_dependency']), 'A menu link with route unmet dependencies is not found.'); + + \Drupal::service('module_installer')->install(['system', 'menu_ui']); + $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); + $definitions = $menu_link_manager->getDefinitions(); + $this->assertTrue(!empty($definitions['menu_dependency_test.route_dependency']), 'Menu link route is found when the dependent module is enabled.'); + } + +} diff --git a/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.info.yml b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.info.yml new file mode 100644 index 0000000..a9edd5c --- /dev/null +++ b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.info.yml @@ -0,0 +1,6 @@ +name: 'Menu dependency tests' +type: module +description: 'Support module for menu dependency testing.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.links.menu.yml b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.links.menu.yml new file mode 100644 index 0000000..cc097f9 --- /dev/null +++ b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.links.menu.yml @@ -0,0 +1,8 @@ +menu_dependency_test.dependency: + title: 'Test menu link dependencies' + route_name: menu_dependency_test.menu_dependency_test + module_dependencies: 'menu_ui' + +menu_dependency_test.route_dependency: + title: 'Test menu link dependencies' + route_name: menu_dependency_test.menu_dependency_route_test diff --git a/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.routing.yml b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.routing.yml new file mode 100644 index 0000000..19bf39b --- /dev/null +++ b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.routing.yml @@ -0,0 +1,14 @@ +menu_dependency_test.menu_dependency_test: + path: '/menu-dependency-test/dependency' + defaults: + _content: '\Drupal\menu_dependency_test\Controller\MenuDependencyTestController::menuDependencyTestCallback' + requirements: + _access: 'TRUE' + +menu_dependency_test.menu_dependency_route_test: + path: '/menu-dependency-test/route-dependency' + defaults: + _content: '\Drupal\menu_dependency_test\Controller\MenuDependencyTestController::menuDependencyTestCallback' + requirements: + _access: 'TRUE' + _module_dependencies: 'menu_ui' diff --git a/core/modules/system/tests/modules/menu_dependency_test/src/Controller/MenuDependencyTestController.php b/core/modules/system/tests/modules/menu_dependency_test/src/Controller/MenuDependencyTestController.php new file mode 100644 index 0000000..9729a20 --- /dev/null +++ b/core/modules/system/tests/modules/menu_dependency_test/src/Controller/MenuDependencyTestController.php @@ -0,0 +1,23 @@ +t('Menu dependency test callback.'); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Plugin/ModuleDependencyTraitTest.php b/core/tests/Drupal/Tests/Core/Plugin/ModuleDependencyTraitTest.php new file mode 100644 index 0000000..8bf4d4a --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Plugin/ModuleDependencyTraitTest.php @@ -0,0 +1,92 @@ +getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $module_handler->expects($this->any()) + ->method('moduleExists') + ->willReturnMap([ + ['existing_module', TRUE], + ['existing_module2', TRUE], + ['not_existing_module', FALSE], + ]); + + $test_plugin_manager = new ModuleDependencyTestPluginManager(); + $test_plugin_manager->setModuleHandler($module_handler); + + $test_definition = []; + // Just a single module dependency. + $test_definition['plugin_1'] = [ + 'id' => 'plugin_1', + 'provider' => 'test_module', + 'module_dependencies' => 'existing_module' + ]; + // Multiple connected via AND. + $test_definition['plugin_2'] = [ + 'id' => 'plugin_2', + 'provider' => 'test_module', + 'module_dependencies' => 'existing_module+existing_module2', + ]; + // An array of module dependencies. + $test_definition['plugin_3'] = [ + 'id' => 'plugin_3', + 'provider' => 'test_module', + 'module_dependencies' => ['existing_module', 'existing_module2'] + ]; + // One missing module. + $test_definition['plugin_4'] = [ + 'id' => 'plugin_4', + 'provider' => 'test_module', + 'module_dependencies' => 'existing_module+not_existing_module', + ]; + // Multiple connected via OR. + $test_definition['plugin_5'] = [ + 'id' => 'plugin_5', + 'provider' => 'test_module', + 'module_dependencies' => 'existing_module,not_existing_module', + ]; + + $test_plugin_manager->runFilterByModuleDependencies($test_definition); + + $this->assertCount(4, $test_definition); + $this->assertEquals(['plugin_1', 'plugin_2', 'plugin_3', 'plugin_5'], array_keys($test_definition)); + } + +} + +class ModuleDependencyTestPluginManager { + + use ModuleDependencyTrait; + + public function runFilterByModuleDependencies(array &$definitions) { + return $this->filterByModuleDependencies($definitions); + } + +}