diff core/lib/Drupal/Core/Menu/MenuLinkManager.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 129ea1e6d783
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/lib/Drupal/Core/Menu/MenuLinkManager.php	Wed Nov 29 16:09:58 2017 +0000
@@ -0,0 +1,416 @@
+<?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 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();
+  }
+
+}