MenuLinkManager.php
Same filename in other branches
Namespace
Drupal\Core\MenuFile
-
core/
lib/ Drupal/ Core/ Menu/ MenuLinkManager.php
View source
<?php
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use Drupal\Core\Plugin\Factory\ContainerFactory;
/**
* Manages discovery, instantiation, and tree building of menu link plugins.
*
* This manager finds plugins that are rendered as menu links.
*/
class MenuLinkManager implements MenuLinkManagerInterface {
/**
* Provides some default values for the definition of all menu link plugins.
*
* @todo Decide how to keep these field definitions in sync.
* https://www.drupal.org/node/2302085
*
* @var array
*/
protected $defaults = [
// (required) The name of the menu for this link.
'menu_name' => 'tools',
// (required) The name of the route this links to, unless it's external.
'route_name' => '',
// Parameters for route variables when generating a link.
'route_parameters' => [],
// The external URL if this link has one (required if route_name is empty).
'url' => '',
// The static title for the menu link. If this came from a YAML definition
// or other safe source this may be a TranslatableMarkup object.
'title' => '',
// The description. If this came from a YAML definition or other safe source
// this may be a TranslatableMarkup object.
'description' => '',
// The plugin ID of the parent link (or NULL for a top-level link).
'parent' => '',
// The weight of the link.
'weight' => 0,
// The default link options.
'options' => [],
'expanded' => 0,
'enabled' => 1,
// The name of the module providing this link.
'provider' => '',
'metadata' => [],
// Default class for local task implementations.
'class' => 'Drupal\\Core\\Menu\\MenuLinkDefault',
'form_class' => 'Drupal\\Core\\Menu\\Form\\MenuLinkDefaultForm',
// The plugin ID. Set by the plugin system based on the top-level YAML key.
'id' => '',
];
/**
* The object that discovers plugins managed by this manager.
*
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
*/
protected $discovery;
/**
* The object that instantiates plugins managed by this manager.
*
* @var \Drupal\Component\Plugin\Factory\FactoryInterface
*/
protected $factory;
/**
* The menu link tree storage.
*
* @var \Drupal\Core\Menu\MenuTreeStorageInterface
*/
protected $treeStorage;
/**
* Service providing overrides for static links.
*
* @var \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
*/
protected $overrides;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a \Drupal\Core\Menu\MenuLinkManager object.
*
* @param \Drupal\Core\Menu\MenuTreeStorageInterface $tree_storage
* The menu link tree storage.
* @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $overrides
* The service providing overrides for static links.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(MenuTreeStorageInterface $tree_storage, StaticMenuLinkOverridesInterface $overrides, ModuleHandlerInterface $module_handler) {
$this->treeStorage = $tree_storage;
$this->overrides = $overrides;
$this->moduleHandler = $module_handler;
}
/**
* Performs extra processing on plugin definitions.
*
* By default we add defaults for the type to the definition. If a type has
* additional processing logic, the logic can be added by replacing or
* extending this method.
*
* @param array $definition
* The definition to be processed and modified by reference.
* @param $plugin_id
* The ID of the plugin this definition is being used for.
*/
protected function processDefinition(array &$definition, $plugin_id) {
$definition = NestedArray::mergeDeep($this->defaults, $definition);
// Typecast so NULL, no parent, will be an empty string since the parent ID
// should be a string.
$definition['parent'] = (string) $definition['parent'];
$definition['id'] = $plugin_id;
}
/**
* Gets the plugin discovery.
*
* @return \Drupal\Component\Plugin\Discovery\DiscoveryInterface
*/
protected function getDiscovery() {
if (!isset($this->discovery)) {
$yaml_discovery = new YamlDiscovery('links.menu', $this->moduleHandler
->getModuleDirectories());
$yaml_discovery->addTranslatableProperty('title', 'title_context');
$yaml_discovery->addTranslatableProperty('description', 'description_context');
$this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery);
}
return $this->discovery;
}
/**
* Gets the plugin factory.
*
* @return \Drupal\Component\Plugin\Factory\FactoryInterface
*/
protected function getFactory() {
if (!isset($this->factory)) {
$this->factory = new ContainerFactory($this);
}
return $this->factory;
}
/**
* {@inheritdoc}
*/
public function getDefinitions() {
// Since this function is called rarely, instantiate the discovery here.
$definitions = $this->getDiscovery()
->getDefinitions();
$this->moduleHandler
->alter('menu_links_discovered', $definitions);
foreach ($definitions as $plugin_id => &$definition) {
$definition['id'] = $plugin_id;
$this->processDefinition($definition, $plugin_id);
}
// If this plugin was provided by a module that does not exist, remove the
// plugin definition.
// @todo Address what to do with an invalid plugin.
// https://www.drupal.org/node/2302623
foreach ($definitions as $plugin_id => $plugin_definition) {
if (!empty($plugin_definition['provider']) && !$this->moduleHandler
->moduleExists($plugin_definition['provider'])) {
unset($definitions[$plugin_id]);
}
}
return $definitions;
}
/**
* {@inheritdoc}
*/
public function rebuild() {
$definitions = $this->getDefinitions();
// Apply overrides from config.
$overrides = $this->overrides
->loadMultipleOverrides(array_keys($definitions));
foreach ($overrides as $id => $changes) {
if (!empty($definitions[$id])) {
$definitions[$id] = $changes + $definitions[$id];
}
}
$this->treeStorage
->rebuild($definitions);
}
/**
* {@inheritdoc}
*/
public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
$definition = $this->treeStorage
->load($plugin_id);
if (empty($definition) && $exception_on_invalid) {
throw new PluginNotFoundException($plugin_id);
}
return $definition;
}
/**
* {@inheritdoc}
*/
public function hasDefinition($plugin_id) {
return (bool) $this->getDefinition($plugin_id, FALSE);
}
/**
* Returns a pre-configured menu link plugin instance.
*
* @param string $plugin_id
* The ID of the plugin being instantiated.
* @param array $configuration
* An array of configuration relevant to the plugin instance.
*
* @return \Drupal\Core\Menu\MenuLinkInterface
* A menu link instance.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If the instance cannot be created, such as if the ID is invalid.
*/
public function createInstance($plugin_id, array $configuration = []) {
return $this->getFactory()
->createInstance($plugin_id, $configuration);
}
/**
* {@inheritdoc}
*/
public function getInstance(array $options) {
if (isset($options['id'])) {
return $this->createInstance($options['id']);
}
}
/**
* {@inheritdoc}
*/
public function deleteLinksInMenu($menu_name) {
foreach ($this->treeStorage
->loadByProperties([
'menu_name' => $menu_name,
]) as $plugin_id => $definition) {
$instance = $this->createInstance($plugin_id);
if ($instance->isDeletable()) {
$this->deleteInstance($instance, TRUE);
}
elseif ($instance->isResettable()) {
$new_instance = $this->resetInstance($instance);
$affected_menus[$new_instance->getMenuName()] = $new_instance->getMenuName();
}
}
}
/**
* Deletes a specific instance.
*
* @param \Drupal\Core\Menu\MenuLinkInterface $instance
* The plugin instance to be deleted.
* @param bool $persist
* If TRUE, calls MenuLinkInterface::deleteLink() on the instance.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If the plugin instance does not support deletion.
*/
protected function deleteInstance(MenuLinkInterface $instance, $persist) {
$id = $instance->getPluginId();
if ($instance->isDeletable()) {
if ($persist) {
$instance->deleteLink();
}
}
else {
throw new PluginException("Menu link plugin with ID '{$id}' does not support deletion");
}
$this->treeStorage
->delete($id);
}
/**
* {@inheritdoc}
*/
public function removeDefinition($id, $persist = TRUE) {
$definition = $this->treeStorage
->load($id);
// It's possible the definition has already been deleted, or doesn't exist.
if ($definition) {
$instance = $this->createInstance($id);
$this->deleteInstance($instance, $persist);
}
}
/**
* {@inheritdoc}
*/
public function menuNameInUse($menu_name) {
$this->treeStorage
->menuNameInUse($menu_name);
}
/**
* {@inheritdoc}
*/
public function countMenuLinks($menu_name = NULL) {
return $this->treeStorage
->countMenuLinks($menu_name);
}
/**
* {@inheritdoc}
*/
public function getParentIds($id) {
if ($this->getDefinition($id, FALSE)) {
return $this->treeStorage
->getRootPathIds($id);
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getChildIds($id) {
if ($this->getDefinition($id, FALSE)) {
return $this->treeStorage
->getAllChildIds($id);
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function loadLinksByRoute($route_name, array $route_parameters = [], $menu_name = NULL) {
$instances = [];
$loaded = $this->treeStorage
->loadByRoute($route_name, $route_parameters, $menu_name);
foreach ($loaded as $plugin_id => $definition) {
$instances[$plugin_id] = $this->createInstance($plugin_id);
}
return $instances;
}
/**
* {@inheritdoc}
*/
public function addDefinition($id, array $definition) {
if ($this->treeStorage
->load($id)) {
throw new PluginException("The menu link ID {$id} already exists as a plugin definition");
}
elseif ($id === '') {
throw new PluginException("The menu link ID cannot be empty");
}
// Add defaults, so there is no requirement to specify everything.
$this->processDefinition($definition, $id);
// Store the new link in the tree.
$this->treeStorage
->save($definition);
return $this->createInstance($id);
}
/**
* {@inheritdoc}
*/
public function updateDefinition($id, array $new_definition_values, $persist = TRUE) {
$instance = $this->createInstance($id);
if ($instance) {
$new_definition_values['id'] = $id;
$changed_definition = $instance->updateLink($new_definition_values, $persist);
$this->treeStorage
->save($changed_definition);
}
return $instance;
}
/**
* {@inheritdoc}
*/
public function resetLink($id) {
$instance = $this->createInstance($id);
$new_instance = $this->resetInstance($instance);
return $new_instance;
}
/**
* Resets the menu link to its default settings.
*
* @param \Drupal\Core\Menu\MenuLinkInterface $instance
* The menu link which should be reset.
*
* @return \Drupal\Core\Menu\MenuLinkInterface
* The reset menu link.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* Thrown when the menu link is not resettable.
*/
protected function resetInstance(MenuLinkInterface $instance) {
$id = $instance->getPluginId();
if (!$instance->isResettable()) {
throw new PluginException("Menu link {$id} is not resettable");
}
// Get the original data from disk, reset the override and re-save the menu
// tree for this link.
$definition = $this->getDefinitions()[$id];
$this->overrides
->deleteOverride($id);
$this->treeStorage
->save($definition);
return $this->createInstance($id);
}
/**
* {@inheritdoc}
*/
public function resetDefinitions() {
$this->treeStorage
->resetDefinitions();
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
MenuLinkManager | Manages discovery, instantiation, and tree building of menu link plugins. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.