Commit 8bb62da2 authored by alexpott's avatar alexpott

Issue #2301239 by pwolanin, dawehner, Wim Leers, effulgentsia, joelpittet,...

Issue #2301239 by pwolanin, dawehner, Wim Leers, effulgentsia, joelpittet, larowlan, xjm, YesCT, kgoel, victoru, berdir, likin, and plach: MenuLinkNG part1 (no UI or conversions): plugins (static + MenuLinkContent) + MenuLinkManager + MenuTreeStorage.
parent 2093f346
......@@ -63,6 +63,13 @@ services:
factory_method: get
factory_service: cache_factory
arguments: [entity]
cache.menu:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
- { name: cache.bin }
factory_method: get
factory_service: cache_factory
arguments: [menu]
cache.render:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
......@@ -265,6 +272,9 @@ services:
plugin.manager.action:
class: Drupal\Core\Action\ActionManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
plugin.manager.menu.link:
class: Drupal\Core\Menu\MenuLinkManager
arguments: ['@menu.tree_storage', '@menu_link.static.overrides', '@module_handler']
plugin.manager.menu.local_action:
class: Drupal\Core\Menu\LocalActionManager
arguments: ['@controller_resolver', '@request_stack', '@router.route_provider', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user']
......@@ -279,6 +289,13 @@ services:
parent: default_plugin_manager
plugin.cache_clearer:
class: Drupal\Core\Plugin\CachedDiscoveryClearer
menu.tree_storage:
class: Drupal\Core\Menu\MenuTreeStorage
arguments: ['@database', '@cache.menu', 'menu_tree']
public: false # Private to plugin.manager.menu.link and menu.link_tree
menu_link.static.overrides:
class: Drupal\Core\Menu\StaticMenuLinkOverrides
arguments: ['@config.factory']
request_stack:
class: Symfony\Component\HttpFoundation\RequestStack
tags:
......
<?php
/**
* @file
* Contains \Drupal\Core\Menu\MenuLinkBase.
*/
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Utility\String;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Url;
/**
* Defines a base menu link class.
*/
abstract class MenuLinkBase extends PluginBase implements MenuLinkInterface {
/**
* The list of definition values where an override is allowed.
*
* The keys are definition names. The values are ignored.
*
* @var array
*/
protected $overrideAllowed = array();
/**
* {@inheritdoc}
*/
public function getWeight() {
// By default the weight is 0.
if (!isset($this->pluginDefinition['weight'])) {
$this->pluginDefinition['weight'] = 0;
}
return $this->pluginDefinition['weight'];
}
/**
* {@inheritdoc}
*/
public function getTitle() {
// Subclasses may pull in the request or specific attributes as parameters.
$options = array();
if (!empty($this->pluginDefinition['title_context'])) {
$options['context'] = $this->pluginDefinition['title_context'];
}
$args = array();
if (isset($this->pluginDefinition['title_arguments']) && $title_arguments = $this->pluginDefinition['title_arguments']) {
$args = (array) $title_arguments;
}
return $this->t($this->pluginDefinition['title'], $args, $options);
}
/**
* {@inheritdoc}
*/
public function getMenuName() {
return $this->pluginDefinition['menu_name'];
}
/**
* {@inheritdoc}
*/
public function getProvider() {
return $this->pluginDefinition['provider'];
}
/**
* {@inheritdoc}
*/
public function getParent() {
return $this->pluginDefinition['parent'];
}
/**
* {@inheritdoc}
*/
public function isHidden() {
return (bool) $this->pluginDefinition['hidden'];
}
/**
* {@inheritdoc}
*/
public function isExpanded() {
return (bool) $this->pluginDefinition['expanded'];
}
/**
* {@inheritdoc}
*/
public function isResetable() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isTranslatable() {
return (bool) $this->getTranslateRoute();
}
/**
* {@inheritdoc}
*/
public function isDeletable() {
return (bool) $this->getDeleteRoute();
}
/**
* {@inheritdoc}
*/
public function getDescription() {
if ($this->pluginDefinition['description']) {
return $this->t($this->pluginDefinition['description']);
}
return '';
}
/**
* {@inheritdoc}
*/
public function getOptions() {
return $this->pluginDefinition['options'] ?: array();
}
/**
* {@inheritdoc}
*/
public function getMetaData() {
return $this->pluginDefinition['metadata'] ?: array();
}
/**
* {@inheritdoc}
*/
public function isCacheable() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getUrlObject($title_attribute = TRUE) {
$options = $this->getOptions();
$description = $this->getDescription();
if ($title_attribute && $description) {
$options['attributes']['title'] = $description;
}
if (empty($this->pluginDefinition['url'])) {
return new Url($this->pluginDefinition['route_name'], $this->pluginDefinition['route_parameters'], $options);
}
else {
$url = Url::createFromPath($this->pluginDefinition['url']);
$url->setOptions($options);
return $url;
}
}
/**
* {@inheritdoc}
*/
public function getFormClass() {
return $this->pluginDefinition['form_class'];
}
/**
* {@inheritdoc}
*/
public function getDeleteRoute() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getEditRoute() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getTranslateRoute() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function deleteLink() {
throw new PluginException(String::format('Menu link plugin with ID @id does not support deletion', array('@id' => $this->getPluginId())));
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Menu\MenuLinkDefault.
*/
namespace Drupal\Core\Menu;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a default implementation for menu link plugins.
*/
class MenuLinkDefault extends MenuLinkBase implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
protected $overrideAllowed = array(
'menu_name' => 1,
'parent' => 1,
'weight' => 1,
'expanded' => 1,
'hidden' => 1,
);
/**
* The static menu link service used to store updates to weight/parent etc.
*
* @var \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
*/
protected $staticOverride;
/**
* Constructs a new MenuLinkDefault.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
* The static override storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->staticOverride = $static_override;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu_link.static.overrides')
);
}
/**
* {@inheritdoc}
*/
public function isResetable() {
// The link can be reset if it has an override.
return (bool) $this->staticOverride->loadOverride($this->getPluginId());
}
/**
* {@inheritdoc}
*/
public function updateLink(array $new_definition_values, $persist) {
// Filter the list of updates to only those that are allowed.
$overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
if ($persist) {
$this->staticOverride->saveOverride($this->getPluginId(), $overrides);
}
// Update the definition.
$this->pluginDefinition = $overrides + $this->getPluginDefinition();
return $this->pluginDefinition;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Menu\MenuLinkInterface.
*/
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Component\Plugin\DerivativeInspectionInterface;
/**
* Defines an interface for classes providing a type of menu link.
*/
interface MenuLinkInterface extends PluginInspectionInterface, DerivativeInspectionInterface {
/**
* Returns the weight of the menu link.
*
* @return int
* The weight of the menu link, 0 by default.
*/
public function getWeight();
/**
* Returns the localized title to be shown for this link.
*
* @return string
* The title of the menu link.
*/
public function getTitle();
/**
* Returns the description of the menu link.
*
* @return string
* The description of the menu link.
*/
public function getDescription();
/**
* Returns the menu name of the menu link.
*
* @return string
* The menu name of the menu link.
*/
public function getMenuName();
/**
* Returns the provider (module name) of the menu link.
*
* @return string
* The provider of the menu link.
*/
public function getProvider();
/**
* Returns the plugin ID of the menu link's parent, or an empty string.
*
* @return string
* The parent plugin ID.
*/
public function getParent();
/**
* Returns whether the menu link is hidden.
*
* @return bool
* TRUE for hidden, FALSE otherwise.
*/
public function isHidden();
/**
* Returns whether the child menu links should always been shown.
*
* @return bool
* TRUE for expanded, FALSE otherwise.
*/
public function isExpanded();
/**
* Returns whether this link can be reset.
*
* In general, only links that store overrides using the
* menu_link.static.overrides service should return TRUE for this method.
*
* @return bool
* TRUE if it can be reset, FALSE otherwise.
*/
public function isResetable();
/**
* Returns whether this link can be translated.
*
* @return bool
* TRUE if the link can be translated, FALSE otherwise.
*/
public function isTranslatable();
/**
* Returns whether this link can be deleted.
*
* @return bool
* TRUE if the link can be deleted, FALSE otherwise.
*/
public function isDeletable();
/**
* Returns a URL object containing either the external path or route.
*
* @param bool $title_attribute
* (optional) If TRUE, add the link description as the title attribute if
* the description is not empty.
*
* @return \Drupal\Core\Url
* A a URL object containing either the external path or route.
*/
public function getUrlObject($title_attribute = TRUE);
/**
* Returns the options for this link.
*
* @return array
* The options for the menu link.
*/
public function getOptions();
/**
* Returns any metadata for this link.
*
* @return array
* The metadata for the menu link.
*/
public function getMetaData();
/**
* Returns whether the rendered link can be cached.
*
* The plugin class may make some or all of the data used in the Url object
* and build array dynamic. For example, it could include the current user
* name in the title, the current time in the description, or a destination
* query string. In addition the route parameters may be dynamic so an access
* check should be performed for each user.
*
* @return bool
* TRUE if the link can be cached, FALSE otherwise.
*/
public function isCacheable();
/**
* Updates the definition values for a menu link.
*
* Depending on the implementation details of the class, not all definition
* values may be changed. For example, changes to the title of a static link
* will be discarded.
*
* In general, this method should not be called directly, but will be called
* automatically from MenuLinkManagerInterface::updateDefinition().
*
* @param array $new_definition_values
* The new values for the link definition. This will usually be just a
* subset of the plugin definition.
* @param bool $persist
* TRUE to have the link persist the changed values to any additional
* storage.
*
* @return array
* The plugin definition incorporating any allowed changes.
*/
public function updateLink(array $new_definition_values, $persist);
/**
* Deletes a menu link.
*
* In general, this method should not be called directly, but will be called
* automatically from MenuLinkManagerInterface::removeDefinition().
*
* This method will only delete the link from any additional storage, but not
* from the plugin.manager.menu.link service.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If the link is not deletable.
*/
public function deleteLink();
/**
* Returns the name of a class that can build an editing form for this link.
*
* To instantiate the form class, use an instance of the
* \Drupal\Core\DependencyInjection\ClassResolverInterface, such as from the
* class_resolver service. Then call the setMenuLinkInstance() method on the
* form instance with the menu link plugin instance.
*
* @todo Add a code example. https://www.drupal.org/node/2302849
*
* @return string
* A class that implements \Drupal\Core\Menu\Form\MenuLinkFormInterface.
*/
public function getFormClass();
/**
* Returns route information for a route to delete the menu link.
*
* @return array|null
* An array with keys route_name and route_parameters, or NULL if there is
* no route (e.g. when the link is not deletable).
*/
public function getDeleteRoute();
/**
* Returns route information for a custom edit form for the menu link.
*
* Plugins should return a value here if they have a special edit form, or if
* they need to define additional local tasks, local actions, etc. that are
* visible from the edit form.
*
* @return array|null
* An array with keys route_name and route_parameters, or NULL if there is
* no route because there is no custom edit route for this instance.
*/
public function getEditRoute();
/**
* Returns route information for a route to translate the menu link.
*
* @return array
* An array with keys route_name and route_parameters, or NULL if there is
* no route (e.g. when the link is not translatable).
*/
public function getTranslateRoute();
}
<?php
/**
* @file
* Contains \Drupal\Core\Menu\MenuLinkManager.
*/
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\String;
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 = array(
// (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' => array(),
// The external URL if this link has one (required if route_name is empty).
'url' => '',
// The static title for the menu link. You can specify placeholders like on
// any translatable string and the values in title_arguments.
'title' => '',
// The values for the menu link placeholders.
'title_arguments' => array(),
// A context for the title string.
// @see \Drupal\Core\StringTranslation\TranslationInterface::translate()
'title_context' => '',
// The description.
'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' => array(),
'expanded' => 0,
'hidden' => 0,
// The name of the module providing this link.
'provider' => '',
'metadata' => array(),
// 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;