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();
}
This diff is collapsed.
<?php
/**
* @file
* Contains \Drupal\Core\Menu\MenuLinkManagerInterface.
*/
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\PluginManagerInterface;
/**
* Defines an interface for managing menu links and storing their definitions.
*
* Menu link managers support both automatic plugin definition discovery and
* manually maintaining plugin definitions.
*
* MenuLinkManagerInterface::updateDefinition() can be used to update a single
* menu link's definition and pass this onto the menu storage without requiring
* a full MenuLinkManagerInterface::rebuild().
*
* Implementations that do not use automatic discovery should call
* MenuLinkManagerInterface::addDefinition() or
* MenuLinkManagerInterface::removeDefinition() when they add or remove links,
* and MenuLinkManagerInterface::updateDefinition() to update links they have
* already defined.
*/
interface MenuLinkManagerInterface extends PluginManagerInterface {
/**
* Triggers discovery, save, and cleanup of discovered links.
*/
public function rebuild();
/**
* Deletes all links having a certain menu name.
*
* If a link is not deletable but is resettable, the link will be reset to have
* its original menu name, under the assumption that the original menu is not
* the one we are deleting it from. Note that when resetting, if the original
* menu name is the same as the menu name passed to this method, the link will
* not be moved or deleted.
*
* @param string $menu_name
* The name of the menu whose links will be deleted or reset.
*/
public function deleteLinksInMenu($menu_name);
/**
* Removes a single link definition from the menu tree storage.
*
* This is used for plugins not found through discovery to remove definitions.
*
* @param string $id
* The menu link plugin ID.
* @param bool $persist
* If TRUE, this method will attempt to persist the deletion from any
* external storage by invoking MenuLinkInterface::deleteLink() on the
* plugin that is being deleted.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* Thrown if the $id is not a valid, existing, plugin ID or if the link
* cannot be deleted.
*/
public function removeDefinition($id, $persist = TRUE);
/**
* Loads multiple plugin instances based on route.
*
* @param string $route_name
* The route name.
* @param array $route_parameters
* (optional) The route parameters. Defaults to an empty array.
* @param string $menu_name
* (optional) Restricts the found links to just those in the named menu.
*
* @return \Drupal\Core\Menu\MenuLinkInterface[]
* An array of instances keyed by plugin ID.
*/
public function loadLinksByRoute($route_name, array $route_parameters = array(), $menu_name = NULL);
/**
* Adds a new menu link definition to the menu tree storage.
*
* Use this function when you know there is no entry in the tree. This is
* used for plugins not found through discovery to add new definitions.
*
* @param string $id
* The plugin ID for the new menu link definition that is being added.
* @param array $definition
* The values of the link definition.
*
* @return \Drupal\Core\Menu\MenuLinkInterface
* A plugin instance created using the newly added definition.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* Thrown when the $id is not valid or is an already existing plugin ID.
*/
public function addDefinition($id, array $definition);
/**
* Updates the values for a menu link definition in the menu tree storage.
*
* This will update the definition for a discovered menu link without the
* need for a full rebuild. It is also used for plugins not found through
* discovery to update definitions.
*
* @param string $id
* The menu link plugin ID.
* @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 also have the link instance itself persist the changed values to
* any additional storage by invoking MenuLinkInterface::updateDefinition()
* on the plugin that is being updated.
*
* @return \Drupal\Core\Menu\MenuLinkInterface
* A plugin instance created using the updated definition.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* Thrown if the $id is not a valid, existing, plugin ID.
*/
public function updateDefinition($id, array $new_definition_values, $persist = TRUE);
/**
* Resets the values for a menu link based on the values found by discovery.
*
* @param string $id
* The menu link plugin ID.
*
* @return \Drupal\Core\Menu\MenuLinkInterface
* The menu link instance after being reset.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* Thrown if the $id is not a valid, existing, plugin ID or if the link
* cannot be reset.
*/
public function resetLink($id);
/**
* Counts the total number of menu links.
*
* @param string $menu_name
* (optional) The menu name to count by. Defaults to all menus.
*
* @return int
* The number of menu links in the named menu, or in all menus if the
* menu name is NULL.
*/
public function countMenuLinks($menu_name = NULL);
/**
* Loads all parent link IDs of a given menu link.
*
* This method is very similar to getActiveTrailIds() but allows the link to
* be specified rather than being discovered based on the menu name and
* request. This method is mostly useful for testing.
*
* @param string $id
* The menu link plugin ID.
*
* @return array
* An ordered array of IDs representing the path to the root of the tree.
* The first element of the array will be equal to $id, unless $id is not
* valid, in which case the return value will be NULL.
*/
public function getParentIds($id);
/**
* Loads all child link IDs of a given menu link, regardless of visibility.
*
* This method is mostly useful for testing.
*