Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
65
core/lib/Drupal/Core/Menu/ContextualLinkDefault.php
Normal file
65
core/lib/Drupal/Core/Menu/ContextualLinkDefault.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\ContextualLinkDefault.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Provides a common base implementation of a contextual link.
|
||||
*/
|
||||
class ContextualLinkDefault extends PluginBase implements ContextualLinkInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @todo: It might be helpful at some point to move this getTitle logic into
|
||||
* a trait.
|
||||
*/
|
||||
public function getTitle(Request $request = NULL) {
|
||||
$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 getRouteName() {
|
||||
return $this->pluginDefinition['route_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroup() {
|
||||
return $this->pluginDefinition['group'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOptions() {
|
||||
return $this->pluginDefinition['options'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->pluginDefinition['weight'];
|
||||
}
|
||||
|
||||
}
|
67
core/lib/Drupal/Core/Menu/ContextualLinkInterface.php
Normal file
67
core/lib/Drupal/Core/Menu/ContextualLinkInterface.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\ContextualLinkInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Defines a contextual link plugin.
|
||||
*/
|
||||
interface ContextualLinkInterface {
|
||||
|
||||
/**
|
||||
* Returns the localized title to be shown for this contextual link.
|
||||
*
|
||||
* Subclasses may add optional arguments like NodeInterface $node = NULL that
|
||||
* will be supplied by the ControllerResolver.
|
||||
*
|
||||
* @return string
|
||||
* The title to be shown for this action.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\ContextualLinksManager::getTitle()
|
||||
*/
|
||||
public function getTitle();
|
||||
|
||||
/**
|
||||
* Returns the route name of the contextual link.
|
||||
*
|
||||
* @return string
|
||||
* The name of the route this contextual link links to.
|
||||
*/
|
||||
public function getRouteName();
|
||||
|
||||
/**
|
||||
* Returns the group this contextual link should be rendered in.
|
||||
*
|
||||
* A contextual link group is a set of contextual links that are displayed
|
||||
* together on a certain page. For example, the 'block' group displays all
|
||||
* links related to the block, such as the block instance edit link as well as
|
||||
* the views edit link, if it is a view block.
|
||||
*
|
||||
* @return string
|
||||
* The contextual links group name.
|
||||
*/
|
||||
public function getGroup();
|
||||
|
||||
/**
|
||||
* Returns the link options passed to the link generator.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of options.
|
||||
*/
|
||||
public function getOptions();
|
||||
|
||||
/**
|
||||
* Returns the weight of the contextual link.
|
||||
*
|
||||
* The contextual links in one group are sorted by weight for display.
|
||||
*
|
||||
* @return int
|
||||
* The weight as positive/negative integer.
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
}
|
198
core/lib/Drupal/Core/Menu/ContextualLinkManager.php
Normal file
198
core/lib/Drupal/Core/Menu/ContextualLinkManager.php
Normal file
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\ContextualLinkManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
|
||||
use Drupal\Core\Plugin\Factory\ContainerFactory;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Defines a contextual link plugin manager to deal with contextual links.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\ContextualLinkInterface
|
||||
*/
|
||||
class ContextualLinkManager extends DefaultPluginManager implements ContextualLinkManagerInterface {
|
||||
|
||||
/**
|
||||
* Provides default values for a contextual link definition.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = array(
|
||||
// (required) The name of the route to link to.
|
||||
'route_name' => '',
|
||||
// (required) The contextual links group.
|
||||
'group' => '',
|
||||
// The static title text for the link.
|
||||
'title' => '',
|
||||
// The default link options.
|
||||
'options' => array(),
|
||||
// The weight of the link.
|
||||
'weight' => NULL,
|
||||
// Default class for contextual link implementations.
|
||||
'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
|
||||
// The plugin id. Set by the plugin system based on the top-level YAML key.
|
||||
'id' => '',
|
||||
);
|
||||
|
||||
/**
|
||||
* A controller resolver object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* A static cache of all the contextual link plugins by group name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pluginsByGroup;
|
||||
|
||||
/**
|
||||
* Constructs a new ContextualLinkManager instance.
|
||||
*
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account, RequestStack $request_stack) {
|
||||
$this->factory = new ContainerFactory($this, '\Drupal\Core\Menu\ContextualLinkInterface');
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->accessManager = $access_manager;
|
||||
$this->account = $account;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->alterInfo('contextual_links_plugins');
|
||||
$this->setCacheBackend($cache_backend, 'contextual_links_plugins:' . $language_manager->getCurrentLanguage()->getId(), array('contextual_links_plugins'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!isset($this->discovery)) {
|
||||
$this->discovery = new YamlDiscovery('links.contextual', $this->moduleHandler->getModuleDirectories());
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
parent::processDefinition($definition, $plugin_id);
|
||||
|
||||
// If there is no route name, this is a broken definition.
|
||||
if (empty($definition['route_name'])) {
|
||||
throw new PluginException(sprintf('Contextual link plugin (%s) definition must include "route_name".', $plugin_id));
|
||||
}
|
||||
// If there is no group name, this is a broken definition.
|
||||
if (empty($definition['group'])) {
|
||||
throw new PluginException(sprintf('Contextual link plugin (%s) definition must include "group".', $plugin_id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContextualLinkPluginsByGroup($group_name) {
|
||||
if (isset($this->pluginsByGroup[$group_name])) {
|
||||
$contextual_links = $this->pluginsByGroup[$group_name];
|
||||
}
|
||||
elseif ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $group_name)) {
|
||||
$contextual_links = $cache->data;
|
||||
$this->pluginsByGroup[$group_name] = $contextual_links;
|
||||
}
|
||||
else {
|
||||
$contextual_links = array();
|
||||
foreach ($this->getDefinitions() as $plugin_id => $plugin_definition) {
|
||||
if ($plugin_definition['group'] == $group_name) {
|
||||
$contextual_links[$plugin_id] = $plugin_definition;
|
||||
}
|
||||
}
|
||||
$this->cacheBackend->set($this->cacheKey . ':' . $group_name, $contextual_links);
|
||||
$this->pluginsByGroup[$group_name] = $contextual_links;
|
||||
}
|
||||
return $contextual_links;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContextualLinksArrayByGroup($group_name, array $route_parameters, array $metadata = array()) {
|
||||
$links = array();
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
foreach ($this->getContextualLinkPluginsByGroup($group_name) as $plugin_id => $plugin_definition) {
|
||||
/** @var $plugin \Drupal\Core\Menu\ContextualLinkInterface */
|
||||
$plugin = $this->createInstance($plugin_id);
|
||||
$route_name = $plugin->getRouteName();
|
||||
|
||||
// Check access.
|
||||
if (!$this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$links[$plugin_id] = array(
|
||||
'route_name' => $route_name,
|
||||
'route_parameters' => $route_parameters,
|
||||
'title' => $plugin->getTitle($request),
|
||||
'weight' => $plugin->getWeight(),
|
||||
'localized_options' => $plugin->getOptions(),
|
||||
'metadata' => $metadata,
|
||||
);
|
||||
}
|
||||
|
||||
$this->moduleHandler->alter('contextual_links', $links, $group_name, $route_parameters);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
}
|
51
core/lib/Drupal/Core/Menu/ContextualLinkManagerInterface.php
Normal file
51
core/lib/Drupal/Core/Menu/ContextualLinkManagerInterface.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\ContextualLinkManagerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Provides an object which returns the available contextual links.
|
||||
*/
|
||||
interface ContextualLinkManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets the contextual link plugins by contextual link group.
|
||||
*
|
||||
* @param string $group_name
|
||||
* The group name.
|
||||
*
|
||||
* @return array
|
||||
* A list of contextual links plugin definitions.
|
||||
*/
|
||||
public function getContextualLinkPluginsByGroup($group_name);
|
||||
|
||||
/**
|
||||
* Gets the contextual links prepared as expected by links.html.twig.
|
||||
*
|
||||
* @param string $group_name
|
||||
* The group name.
|
||||
* @param array $route_parameters
|
||||
* The incoming route parameters. The route parameters need to have the same
|
||||
* name on all contextual link routes, e.g. you cannot use 'node' and
|
||||
* 'entity' in parallel.
|
||||
* @param array $metadata
|
||||
* Additional metadata of contextual links, like the position (optional).
|
||||
*
|
||||
* @return array
|
||||
* An array of link information, keyed by the plugin ID. Each entry is an
|
||||
* associative array with the following keys:
|
||||
* - route_name: The route name to link to.
|
||||
* - route_parameters: The route parameters for the contextual link.
|
||||
* - title: The title of the contextual link.
|
||||
* - weight: The weight of the contextual link.
|
||||
* - localized_options: The options of the link, which will be passed
|
||||
* to the link generator.
|
||||
* - metadata: The array of additional metadata that was passed in.
|
||||
*/
|
||||
public function getContextualLinksArrayByGroup($group_name, array $route_parameters, array $metadata = array());
|
||||
|
||||
}
|
268
core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
Normal file
268
core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
Normal file
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Provides a couple of menu link tree manipulators.
|
||||
*
|
||||
* This class provides menu link tree manipulators to:
|
||||
* - perform render cached menu-optimized access checking
|
||||
* - optimized node access checking
|
||||
* - generate a unique index for the elements in a tree and sorting by it
|
||||
* - flatten a tree (i.e. a 1-dimensional tree)
|
||||
*/
|
||||
class DefaultMenuLinkTreeManipulators {
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The entity query factory.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $queryFactory;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators object.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* The entity query factory.
|
||||
*/
|
||||
public function __construct(AccessManagerInterface $access_manager, AccountInterface $account, QueryFactory $query_factory) {
|
||||
$this->accessManager = $access_manager;
|
||||
$this->account = $account;
|
||||
$this->queryFactory = $query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs access checks of a menu tree.
|
||||
*
|
||||
* Sets the 'access' property to AccessResultInterface objects on menu link
|
||||
* tree elements. Descends into subtrees if the root of the subtree is
|
||||
* accessible. Inaccessible subtrees are deleted, except the top-level
|
||||
* inaccessible link, to be compatible with render caching.
|
||||
*
|
||||
* (This means that top-level inaccessible links are *not* removed; it is up
|
||||
* to the code doing something with the tree to exclude inaccessible links,
|
||||
* just like MenuLinkTree::build() does. This allows those things to specify
|
||||
* the necessary cacheability metadata.)
|
||||
*
|
||||
* This is compatible with render caching, because of cache context bubbling:
|
||||
* conditionally defined cache contexts (i.e. subtrees that are only
|
||||
* accessible to some users) will bubble just like they do for render arrays.
|
||||
* This is why inaccessible subtrees are deleted, except at the top-level
|
||||
* inaccessible link: if we didn't keep the first (depth-wise) inaccessible
|
||||
* link, we wouldn't be able to know which cache contexts would cause those
|
||||
* subtrees to become accessible again, thus forcing us to conclude that that
|
||||
* subtree is unconditionally inaccessible.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* The menu link tree to manipulate.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* The manipulated menu link tree.
|
||||
*/
|
||||
public function checkAccess(array $tree) {
|
||||
foreach ($tree as $key => $element) {
|
||||
// Other menu tree manipulators may already have calculated access, do not
|
||||
// overwrite the existing value in that case.
|
||||
if (!isset($element->access)) {
|
||||
$tree[$key]->access = $this->menuLinkCheckAccess($element->link);
|
||||
}
|
||||
if ($tree[$key]->access->isAllowed()) {
|
||||
if ($tree[$key]->subtree) {
|
||||
$tree[$key]->subtree = $this->checkAccess($tree[$key]->subtree);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Replace the link with an InaccessibleMenuLink object, so that if it
|
||||
// is accidentally rendered, no sensitive information is divulged.
|
||||
$tree[$key]->link = new InaccessibleMenuLink($tree[$key]->link);
|
||||
// Always keep top-level inaccessible links: their cacheability metadata
|
||||
// that indicates why they're not accessible by the current user must be
|
||||
// bubbled. Otherwise, those subtrees will not be varied by any cache
|
||||
// contexts at all, therefore forcing them to remain empty for all users
|
||||
// unless some other part of the menu link tree accidentally varies by
|
||||
// the same cache contexts.
|
||||
// For deeper levels, we *can* remove the subtrees and therefore also
|
||||
// not perform access checking on the subtree, thanks to bubbling/cache
|
||||
// redirects. This therefore allows us to still do significantly less
|
||||
// work in case of inaccessible subtrees, which is the entire reason why
|
||||
// this deletes subtrees in the first place.
|
||||
$tree[$key]->subtree = [];
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs access checking for nodes in an optimized way.
|
||||
*
|
||||
* This manipulator should be added before the generic ::checkAccess() one,
|
||||
* because it provides a performance optimization for ::checkAccess().
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* The menu link tree to manipulate.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* The manipulated menu link tree.
|
||||
*/
|
||||
public function checkNodeAccess(array $tree) {
|
||||
$node_links = array();
|
||||
$this->collectNodeLinks($tree, $node_links);
|
||||
if ($node_links) {
|
||||
$nids = array_keys($node_links);
|
||||
|
||||
$query = $this->queryFactory->get('node');
|
||||
$query->condition('nid', $nids, 'IN');
|
||||
|
||||
// Allows admins to view all nodes, by both disabling node_access
|
||||
// query rewrite as well as not checking for the node status. The
|
||||
// 'view own unpublished nodes' permission is ignored to not require cache
|
||||
// entries per user.
|
||||
$access_result = AccessResult::allowed()->cachePerPermissions();
|
||||
if ($this->account->hasPermission('bypass node access')) {
|
||||
$query->accessCheck(FALSE);
|
||||
}
|
||||
else {
|
||||
$access_result->addCacheContexts(['user.node_grants:view']);
|
||||
$query->condition('status', NODE_PUBLISHED);
|
||||
}
|
||||
|
||||
$nids = $query->execute();
|
||||
foreach ($nids as $nid) {
|
||||
foreach ($node_links[$nid] as $key => $link) {
|
||||
$node_links[$nid][$key]->access = $access_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the node links in the menu tree.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* The menu link tree to manipulate.
|
||||
* @param array $node_links
|
||||
* Stores references to menu link elements to effectively set access.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* The manipulated menu link tree.
|
||||
*/
|
||||
protected function collectNodeLinks(array &$tree, array &$node_links) {
|
||||
foreach ($tree as $key => &$element) {
|
||||
if ($element->link->getRouteName() == 'entity.node.canonical') {
|
||||
$nid = $element->link->getRouteParameters()['node'];
|
||||
$node_links[$nid][$key] = $element;
|
||||
// Deny access by default. checkNodeAccess() will re-add it.
|
||||
$element->access = AccessResult::neutral();
|
||||
}
|
||||
if ($element->hasChildren) {
|
||||
$this->collectNodeLinks($element->subtree, $node_links);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for one menu link instance.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkInterface $instance
|
||||
* The menu link instance.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
protected function menuLinkCheckAccess(MenuLinkInterface $instance) {
|
||||
$access_result = NULL;
|
||||
if ($this->account->hasPermission('link to any page')) {
|
||||
$access_result = AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
// Use the definition here since that's a lot faster than creating a Url
|
||||
// object that we don't need.
|
||||
$definition = $instance->getPluginDefinition();
|
||||
// 'url' should only be populated for external links.
|
||||
if (!empty($definition['url']) && empty($definition['route_name'])) {
|
||||
$access_result = AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
$access_result = $this->accessManager->checkNamedRoute($definition['route_name'], $definition['route_parameters'], $this->account, TRUE);
|
||||
}
|
||||
}
|
||||
return $access_result->cachePerPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique index and sorts by it.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* The menu link tree to manipulate.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* The manipulated menu link tree.
|
||||
*/
|
||||
public function generateIndexAndSort(array $tree) {
|
||||
$new_tree = array();
|
||||
foreach ($tree as $key => $v) {
|
||||
if ($tree[$key]->subtree) {
|
||||
$tree[$key]->subtree = $this->generateIndexAndSort($tree[$key]->subtree);
|
||||
}
|
||||
$instance = $tree[$key]->link;
|
||||
// The weights are made a uniform 5 digits by adding 50000 as an offset.
|
||||
// After $this->menuLinkCheckAccess(), $instance->getTitle() has the
|
||||
// localized or translated title. Adding the plugin id to the end of the
|
||||
// index insures that it is unique.
|
||||
$new_tree[(50000 + $instance->getWeight()) . ' ' . $instance->getTitle() . ' ' . $instance->getPluginId()] = $tree[$key];
|
||||
}
|
||||
ksort($new_tree);
|
||||
return $new_tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens the tree to a single level.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* The menu link tree to manipulate.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* The manipulated menu link tree.
|
||||
*/
|
||||
public function flatten(array $tree) {
|
||||
foreach ($tree as $key => $element) {
|
||||
if ($tree[$key]->subtree) {
|
||||
$tree += $this->flatten($tree[$key]->subtree);
|
||||
}
|
||||
$tree[$key]->subtree = array();
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
}
|
190
core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
Normal file
190
core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\Form\MenuLinkDefaultForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu\Form;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Menu\MenuLinkInterface;
|
||||
use Drupal\Core\Menu\MenuLinkManagerInterface;
|
||||
use Drupal\Core\Menu\MenuParentFormSelectorInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an edit form for static menu links.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\MenuLinkDefault
|
||||
*/
|
||||
class MenuLinkDefaultForm implements MenuLinkFormInterface, ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The edited menu link.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkInterface
|
||||
*/
|
||||
protected $menuLink;
|
||||
|
||||
/**
|
||||
* The menu link manager.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
|
||||
*/
|
||||
protected $menuLinkManager;
|
||||
|
||||
/**
|
||||
* The parent form selector service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuParentFormSelectorInterface
|
||||
*/
|
||||
protected $menuParentSelector;
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The module data from system_get_info().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $moduleData;
|
||||
|
||||
/**
|
||||
* Constructs a new \Drupal\Core\Menu\Form\MenuLinkDefaultForm.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
|
||||
* The menu link manager.
|
||||
* @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector
|
||||
* The menu parent form selector service.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler;
|
||||
*/
|
||||
public function __construct(MenuLinkManagerInterface $menu_link_manager, MenuParentFormSelectorInterface $menu_parent_selector, TranslationInterface $string_translation, ModuleHandlerInterface $module_handler) {
|
||||
$this->menuLinkManager = $menu_link_manager;
|
||||
$this->menuParentSelector = $menu_parent_selector;
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.menu.link'),
|
||||
$container->get('menu.parent_form_selector'),
|
||||
$container->get('string_translation'),
|
||||
$container->get('module_handler')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMenuLinkInstance(MenuLinkInterface $menu_link) {
|
||||
$this->menuLink = $menu_link;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['#title'] = $this->t('Edit menu link %title', array('%title' => $this->menuLink->getTitle()));
|
||||
|
||||
$provider = $this->menuLink->getProvider();
|
||||
$form['info'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('This link is provided by the @name module. The title and path cannot be edited.', array('@name' => $this->moduleHandler->getName($provider))),
|
||||
);
|
||||
$link = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->menuLink->getTitle(),
|
||||
'#url' => $this->menuLink->getUrlObject(),
|
||||
);
|
||||
$form['path'] = array(
|
||||
'link' => $link,
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Link'),
|
||||
);
|
||||
|
||||
$form['enabled'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Enable menu link'),
|
||||
'#description' => $this->t('Menu links that are not enabled will not be listed in any menu.'),
|
||||
'#default_value' => $this->menuLink->isEnabled(),
|
||||
);
|
||||
|
||||
$form['expanded'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Show as expanded'),
|
||||
'#description' => $this->t('If selected and this menu link has children, the menu will always appear expanded.'),
|
||||
'#default_value' => $this->menuLink->isExpanded(),
|
||||
);
|
||||
|
||||
$menu_parent = $this->menuLink->getMenuName() . ':' . $this->menuLink->getParent();
|
||||
$form['menu_parent'] = $this->menuParentSelector->parentSelectElement($menu_parent, $this->menuLink->getPluginId());
|
||||
$form['menu_parent']['#title'] = $this->t('Parent link');
|
||||
$form['menu_parent']['#description'] = $this->t('The maximum depth for a link and all its children is fixed. Some menu links may not be available as parents if selecting them would exceed this limit.');
|
||||
$form['menu_parent']['#attributes']['class'][] = 'menu-title-select';
|
||||
|
||||
$delta = max(abs($this->menuLink->getWeight()), 50);
|
||||
$form['weight'] = array(
|
||||
'#type' => 'number',
|
||||
'#min' => -$delta,
|
||||
'#max' => $delta,
|
||||
'#default_value' => $this->menuLink->getWeight(),
|
||||
'#title' => $this->t('Weight'),
|
||||
'#description' => $this->t('Link weight among links in the same menu at the same depth. In the menu, the links with high weight will sink and links with a low weight will be positioned nearer the top.'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extractFormValues(array &$form, FormStateInterface $form_state) {
|
||||
$new_definition = array();
|
||||
$new_definition['enabled'] = $form_state->getValue('enabled') ? 1 : 0;
|
||||
$new_definition['weight'] = (int) $form_state->getValue('weight');
|
||||
$new_definition['expanded'] = $form_state->getValue('expanded') ? 1 : 0;
|
||||
list($menu_name, $parent) = explode(':', $form_state->getValue('menu_parent'), 2);
|
||||
if (!empty($menu_name)) {
|
||||
$new_definition['menu_name'] = $menu_name;
|
||||
}
|
||||
if (isset($parent)) {
|
||||
$new_definition['parent'] = $parent;
|
||||
}
|
||||
return $new_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$new_definition = $this->extractFormValues($form, $form_state);
|
||||
|
||||
return $this->menuLinkManager->updateDefinition($this->menuLink->getPluginId(), $new_definition);
|
||||
}
|
||||
|
||||
}
|
45
core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php
Normal file
45
core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\Form\MenuLinkFormInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Menu\MenuLinkInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for edit forms of menu links.
|
||||
*
|
||||
* All menu link plugins use the same interface for their configuration or
|
||||
* editing form, but the implementations may differ.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\MenuLinkInterface::getFormClass()
|
||||
*/
|
||||
interface MenuLinkFormInterface extends PluginFormInterface {
|
||||
|
||||
/**
|
||||
* Injects the menu link plugin instance.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkInterface $menu_link
|
||||
* A menu link plugin instance.
|
||||
*/
|
||||
public function setMenuLinkInstance(MenuLinkInterface $menu_link);
|
||||
|
||||
/**
|
||||
* Extracts a plugin definition from form values.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* The new plugin definition values taken from the form values.
|
||||
*/
|
||||
public function extractFormValues(array &$form, FormStateInterface $form_state);
|
||||
|
||||
}
|
86
core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php
Normal file
86
core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\InaccessibleMenuLink.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* A menu link plugin for wrapping another menu link, in sensitive situations.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators::checkAccess()
|
||||
*/
|
||||
class InaccessibleMenuLink extends MenuLinkBase {
|
||||
|
||||
/**
|
||||
* The wrapped menu link.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkInterface
|
||||
*/
|
||||
protected $wrappedLink;
|
||||
|
||||
/**
|
||||
* Constructs a new InaccessibleMenuLink.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkInterface $wrapped_link
|
||||
* The menu link to wrap.
|
||||
*/
|
||||
public function __construct(MenuLinkInterface $wrapped_link) {
|
||||
$this->wrappedLink = $wrapped_link;
|
||||
$plugin_definition = [
|
||||
'route_name' => '<front>',
|
||||
'route_parameters' => [],
|
||||
'url' => NULL,
|
||||
] + $this->wrappedLink->getPluginDefinition();
|
||||
parent::__construct([], $this->wrappedLink->getPluginId(), $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle() {
|
||||
return $this->t('Inaccessible');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return $this->wrappedLink->getCacheContexts();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->wrappedLink->getCacheTags();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->wrappedLink->getCacheMaxAge();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateLink(array $new_definition_values, $persist) {
|
||||
throw new PluginException(SafeMarkup::format('Inaccessible menu link plugins do not support updating'));
|
||||
}
|
||||
|
||||
}
|
129
core/lib/Drupal/Core/Menu/LocalActionDefault.php
Normal file
129
core/lib/Drupal/Core/Menu/LocalActionDefault.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalActionDefault.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Provides a default implementation for local action plugins.
|
||||
*/
|
||||
class LocalActionDefault extends PluginBase implements LocalActionInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The route provider to load routes by name.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* Constructs a LocalActionDefault object.
|
||||
*
|
||||
* @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\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider to load routes by name.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->routeProvider = $route_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('router.route_provider')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName() {
|
||||
return $this->pluginDefinition['route_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle(Request $request = NULL) {
|
||||
// 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 getWeight() {
|
||||
return $this->pluginDefinition['weight'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteParameters(RouteMatchInterface $route_match) {
|
||||
$parameters = isset($this->pluginDefinition['route_parameters']) ? $this->pluginDefinition['route_parameters'] : array();
|
||||
$route = $this->routeProvider->getRouteByName($this->getRouteName());
|
||||
$variables = $route->compile()->getVariables();
|
||||
|
||||
// Normally the \Drupal\Core\ParamConverter\ParamConverterManager has
|
||||
// processed the Request attributes, and in that case the _raw_variables
|
||||
// attribute holds the original path strings keyed to the corresponding
|
||||
// slugs in the path patterns. For example, if the route's path pattern is
|
||||
// /filter/tips/{filter_format} and the path is /filter/tips/plain_text then
|
||||
// $raw_variables->get('filter_format') == 'plain_text'.
|
||||
$raw_variables = $route_match->getRawParameters();
|
||||
|
||||
foreach ($variables as $name) {
|
||||
if (isset($parameters[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($raw_variables && $raw_variables->has($name)) {
|
||||
$parameters[$name] = $raw_variables->get($name);
|
||||
}
|
||||
elseif ($value = $route_match->getRawParameter($name)) {
|
||||
$parameters[$name] = $value;
|
||||
}
|
||||
}
|
||||
// The UrlGenerator will throw an exception if expected parameters are
|
||||
// missing. This method should be overridden if that is possible.
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOptions(RouteMatchInterface $route_match) {
|
||||
return (array) $this->pluginDefinition['options'];
|
||||
}
|
||||
|
||||
}
|
69
core/lib/Drupal/Core/Menu/LocalActionInterface.php
Normal file
69
core/lib/Drupal/Core/Menu/LocalActionInterface.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalActionInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for menu local actions.
|
||||
*/
|
||||
interface LocalActionInterface {
|
||||
|
||||
/**
|
||||
* Get the route name from the settings.
|
||||
*
|
||||
* @return string
|
||||
* The name of the route this action links to.
|
||||
*/
|
||||
public function getRouteName();
|
||||
|
||||
/**
|
||||
* Returns the route parameters needed to render a link for the local action.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*
|
||||
* @return array
|
||||
* An array of parameter names and values.
|
||||
*/
|
||||
public function getRouteParameters(RouteMatchInterface $route_match);
|
||||
|
||||
/**
|
||||
* Returns the weight for the local action.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
/**
|
||||
* Returns options for rendering a link for the local action.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of options.
|
||||
*/
|
||||
public function getOptions(RouteMatchInterface $route_match);
|
||||
|
||||
/**
|
||||
* Returns the localized title to be shown for this action.
|
||||
*
|
||||
* Subclasses may add optional arguments like NodeInterface $node = NULL that
|
||||
* will be supplied by the ControllerResolver.
|
||||
*
|
||||
* @return string
|
||||
* The title to be shown for this action.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\LocalActionManager::getTitle()
|
||||
*/
|
||||
public function getTitle();
|
||||
|
||||
|
||||
|
||||
}
|
200
core/lib/Drupal/Core/Menu/LocalActionManager.php
Normal file
200
core/lib/Drupal/Core/Menu/LocalActionManager.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalActionManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
|
||||
use Drupal\Core\Plugin\Factory\ContainerFactory;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Provides the default local action manager using YML as primary definition.
|
||||
*/
|
||||
class LocalActionManager extends DefaultPluginManager implements LocalActionManagerInterface {
|
||||
|
||||
/**
|
||||
* Provides some default values for all local action plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = array(
|
||||
// The plugin id. Set by the plugin system based on the top-level YAML key.
|
||||
'id' => NULL,
|
||||
// The static title for the local action.
|
||||
'title' => '',
|
||||
// The weight of the local action.
|
||||
'weight' => NULL,
|
||||
// (Required) the route name used to generate a link.
|
||||
'route_name' => NULL,
|
||||
// Default route parameters for generating links.
|
||||
'route_parameters' => array(),
|
||||
// Associative array of link options.
|
||||
'options' => array(),
|
||||
// The route names where this local action appears.
|
||||
'appears_on' => array(),
|
||||
// Default class for local action implementations.
|
||||
'class' => 'Drupal\Core\Menu\LocalActionDefault',
|
||||
);
|
||||
|
||||
/**
|
||||
* A controller resolver object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* The route provider to load routes by name.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The plugin instances.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\LocalActionInterface[]
|
||||
*/
|
||||
protected $instances = array();
|
||||
|
||||
/**
|
||||
* Constructs a LocalActionManager object.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface $controller_resolver
|
||||
* An object to use in introspecting route methods.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) {
|
||||
// Skip calling the parent constructor, since that assumes annotation-based
|
||||
// discovery.
|
||||
$this->factory = new ContainerFactory($this, 'Drupal\Core\Menu\LocalActionInterface');
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->routeMatch = $route_match;
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->accessManager = $access_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->account = $account;
|
||||
$this->alterInfo('menu_local_actions');
|
||||
$this->setCacheBackend($cache_backend, 'local_action_plugins:' . $language_manager->getCurrentLanguage()->getId(), array('local_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!isset($this->discovery)) {
|
||||
$this->discovery = new YamlDiscovery('links.action', $this->moduleHandler->getModuleDirectories());
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle(LocalActionInterface $local_action) {
|
||||
$controller = array($local_action, 'getTitle');
|
||||
$arguments = $this->controllerResolver->getArguments($this->requestStack->getCurrentRequest(), $controller);
|
||||
return call_user_func_array($controller, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getActionsForRoute($route_appears) {
|
||||
if (!isset($this->instances[$route_appears])) {
|
||||
$route_names = array();
|
||||
$this->instances[$route_appears] = array();
|
||||
// @todo - optimize this lookup by compiling or caching.
|
||||
foreach ($this->getDefinitions() as $plugin_id => $action_info) {
|
||||
if (in_array($route_appears, $action_info['appears_on'])) {
|
||||
$plugin = $this->createInstance($plugin_id);
|
||||
$route_names[] = $plugin->getRouteName();
|
||||
$this->instances[$route_appears][$plugin_id] = $plugin;
|
||||
}
|
||||
}
|
||||
// Pre-fetch all the action route objects. This reduces the number of SQL
|
||||
// queries that would otherwise be triggered by the access manager.
|
||||
if (!empty($route_names)) {
|
||||
$this->routeProvider->getRoutesByNames($route_names);
|
||||
}
|
||||
}
|
||||
$links = array();
|
||||
/** @var $plugin \Drupal\Core\Menu\LocalActionInterface */
|
||||
foreach ($this->instances[$route_appears] as $plugin_id => $plugin) {
|
||||
$route_name = $plugin->getRouteName();
|
||||
$route_parameters = $plugin->getRouteParameters($this->routeMatch);
|
||||
$links[$plugin_id] = array(
|
||||
'#theme' => 'menu_local_action',
|
||||
'#link' => array(
|
||||
'title' => $this->getTitle($plugin),
|
||||
'url' => Url::fromRoute($route_name, $route_parameters),
|
||||
'localized_options' => $plugin->getOptions($this->routeMatch),
|
||||
),
|
||||
'#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account),
|
||||
'#weight' => $plugin->getWeight(),
|
||||
);
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
|
||||
}
|
46
core/lib/Drupal/Core/Menu/LocalActionManagerInterface.php
Normal file
46
core/lib/Drupal/Core/Menu/LocalActionManagerInterface.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalActionManagerInterface.
|
||||
*/
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Manages discovery and instantiation of menu local action plugins.
|
||||
*
|
||||
* Menu local actions are links that lead to actions like "add new". The plugin
|
||||
* format allows them (if needed) to dynamically generate a title or the path
|
||||
* they link to. The annotation on the plugin provides the default title,
|
||||
* and the list of routes where the action should be rendered.
|
||||
*/
|
||||
interface LocalActionManagerInterface extends PluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets the title for a local action.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\LocalActionInterface $local_action
|
||||
* An object to get the title from.
|
||||
*
|
||||
* @return string
|
||||
* The title (already localized).
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
* If the plugin does not implement the getTitle() method.
|
||||
*/
|
||||
public function getTitle(LocalActionInterface $local_action);
|
||||
|
||||
/**
|
||||
* Finds all local actions that appear on a named route.
|
||||
*
|
||||
* @param string $route_appears
|
||||
* The route name for which to find local actions.
|
||||
*
|
||||
* @return array
|
||||
* An array of link render arrays.
|
||||
*/
|
||||
public function getActionsForRoute($route_appears);
|
||||
|
||||
}
|
151
core/lib/Drupal/Core/Menu/LocalTaskDefault.php
Normal file
151
core/lib/Drupal/Core/Menu/LocalTaskDefault.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalTaskDefault.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Default object used for LocalTaskPlugins.
|
||||
*/
|
||||
class LocalTaskDefault extends PluginBase implements LocalTaskInterface {
|
||||
|
||||
/**
|
||||
* The route provider to load routes by name.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* TRUE if this plugin is forced active for options attributes.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $active = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName() {
|
||||
return $this->pluginDefinition['route_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteParameters(RouteMatchInterface $route_match) {
|
||||
$parameters = isset($this->pluginDefinition['route_parameters']) ? $this->pluginDefinition['route_parameters'] : array();
|
||||
$route = $this->routeProvider()->getRouteByName($this->getRouteName());
|
||||
$variables = $route->compile()->getVariables();
|
||||
|
||||
// Normally the \Drupal\Core\ParamConverter\ParamConverterManager has
|
||||
// processed the Request attributes, and in that case the _raw_variables
|
||||
// attribute holds the original path strings keyed to the corresponding
|
||||
// slugs in the path patterns. For example, if the route's path pattern is
|
||||
// /filter/tips/{filter_format} and the path is /filter/tips/plain_text then
|
||||
// $raw_variables->get('filter_format') == 'plain_text'.
|
||||
|
||||
$raw_variables = $route_match->getRawParameters();
|
||||
|
||||
foreach ($variables as $name) {
|
||||
if (isset($parameters[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($raw_variables && $raw_variables->has($name)) {
|
||||
$parameters[$name] = $raw_variables->get($name);
|
||||
}
|
||||
elseif ($value = $route_match->getRawParameter($name)) {
|
||||
$parameters[$name] = $value;
|
||||
}
|
||||
}
|
||||
// The UrlGenerator will throw an exception if expected parameters are
|
||||
// missing. This method should be overridden if that is possible.
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle(Request $request = NULL) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the weight of the local task.
|
||||
*
|
||||
* @return int
|
||||
* The weight of the task. If not defined in the annotation returns 0 by
|
||||
* default or -10 for the root tab.
|
||||
*/
|
||||
public function getWeight() {
|
||||
// By default the weight is 0, or -10 for the root tab.
|
||||
if (!isset($this->pluginDefinition['weight'])) {
|
||||
if ($this->pluginDefinition['base_route'] == $this->pluginDefinition['route_name']) {
|
||||
$this->pluginDefinition['weight'] = -10;
|
||||
}
|
||||
else {
|
||||
$this->pluginDefinition['weight'] = 0;
|
||||
}
|
||||
}
|
||||
return (int) $this->pluginDefinition['weight'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOptions(RouteMatchInterface $route_match) {
|
||||
$options = $this->pluginDefinition['options'];
|
||||
if ($this->active) {
|
||||
if (empty($options['attributes']['class']) || !in_array('is-active', $options['attributes']['class'])) {
|
||||
$options['attributes']['class'][] = 'is-active';
|
||||
}
|
||||
}
|
||||
return (array) $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setActive($active = TRUE) {
|
||||
$this->active = $active;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getActive() {
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the route provider.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteProviderInterface
|
||||
* The route provider.
|
||||
*/
|
||||
protected function routeProvider() {
|
||||
if (!$this->routeProvider) {
|
||||
$this->routeProvider = \Drupal::service('router.route_provider');
|
||||
}
|
||||
return $this->routeProvider;
|
||||
}
|
||||
|
||||
}
|
87
core/lib/Drupal/Core/Menu/LocalTaskInterface.php
Normal file
87
core/lib/Drupal/Core/Menu/LocalTaskInterface.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalTaskInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for menu local tasks.
|
||||
*/
|
||||
interface LocalTaskInterface {
|
||||
|
||||
/**
|
||||
* Get the route name from the settings.
|
||||
*
|
||||
* @return string
|
||||
* The name of the route this local task links to.
|
||||
*/
|
||||
public function getRouteName();
|
||||
|
||||
/**
|
||||
* Returns the localized title to be shown for this tab.
|
||||
*
|
||||
* Subclasses may add optional arguments like NodeInterface $node = NULL that
|
||||
* will be supplied by the ControllerResolver.
|
||||
*
|
||||
* @return string
|
||||
* The title of the local task.
|
||||
*/
|
||||
public function getTitle();
|
||||
|
||||
/**
|
||||
* Returns the route parameters needed to render a link for the local task.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*
|
||||
* @return array
|
||||
* An array of parameter names and values.
|
||||
*/
|
||||
public function getRouteParameters(RouteMatchInterface $route_match);
|
||||
|
||||
/**
|
||||
* Returns the weight of the local task.
|
||||
*
|
||||
* @return int|null
|
||||
* The weight of the task or NULL.
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
/**
|
||||
* Returns options for rendering a link to the local task.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of options.
|
||||
*/
|
||||
public function getOptions(RouteMatchInterface $route_match);
|
||||
|
||||
/**
|
||||
* Sets the active status.
|
||||
*
|
||||
* @param bool $active
|
||||
* Sets whether this tab is active (e.g. a parent of the current tab).
|
||||
*
|
||||
* @return \Drupal\Core\Menu\LocalTaskInterface
|
||||
* The called object for chaining.
|
||||
*/
|
||||
public function setActive($active = TRUE);
|
||||
|
||||
/**
|
||||
* Gets the active status.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the local task is active, FALSE otherwise.
|
||||
*
|
||||
* @see \Drupal\system\Plugin\MenuLocalTaskInterface::setActive()
|
||||
*/
|
||||
public function getActive();
|
||||
|
||||
}
|
361
core/lib/Drupal/Core/Menu/LocalTaskManager.php
Normal file
361
core/lib/Drupal/Core/Menu/LocalTaskManager.php
Normal file
|
@ -0,0 +1,361 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalTaskManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
|
||||
use Drupal\Core\Plugin\Factory\ContainerFactory;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Provides the default local task manager using YML as primary definition.
|
||||
*/
|
||||
class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaults = array(
|
||||
// (required) The name of the route this task links to.
|
||||
'route_name' => '',
|
||||
// Parameters for route variables when generating a link.
|
||||
'route_parameters' => array(),
|
||||
// The static title for the local task.
|
||||
'title' => '',
|
||||
// The route name where the root tab appears.
|
||||
'base_route' => '',
|
||||
// The plugin ID of the parent tab (or NULL for the top-level tab).
|
||||
'parent_id' => NULL,
|
||||
// The weight of the tab.
|
||||
'weight' => NULL,
|
||||
// The default link options.
|
||||
'options' => array(),
|
||||
// Default class for local task implementations.
|
||||
'class' => 'Drupal\Core\Menu\LocalTaskDefault',
|
||||
// The plugin id. Set by the plugin system based on the top-level YAML key.
|
||||
'id' => '',
|
||||
);
|
||||
|
||||
/**
|
||||
* A controller resolver object.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* The plugin instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $instances = array();
|
||||
|
||||
/**
|
||||
* The route provider to load routes by name.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Menu\LocalTaskManager object.
|
||||
*
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* An object to use in introspecting route methods.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request object to use for building titles and paths for plugin instances.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider to load routes by name.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) {
|
||||
$this->factory = new ContainerFactory($this, '\Drupal\Core\Menu\LocalTaskInterface');
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->routeMatch = $route_match;
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->accessManager = $access_manager;
|
||||
$this->account = $account;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->alterInfo('local_tasks');
|
||||
$this->setCacheBackend($cache, 'local_task_plugins:' . $language_manager->getCurrentLanguage()->getId(), array('local_task'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!isset($this->discovery)) {
|
||||
$this->discovery = new YamlDiscovery('links.task', $this->moduleHandler->getModuleDirectories());
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
parent::processDefinition($definition, $plugin_id);
|
||||
// If there is no route name, this is a broken definition.
|
||||
if (empty($definition['route_name'])) {
|
||||
throw new PluginException(sprintf('Plugin (%s) definition must include "route_name"', $plugin_id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle(LocalTaskInterface $local_task) {
|
||||
$controller = array($local_task, 'getTitle');
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
$arguments = $this->controllerResolver->getArguments($request, $controller);
|
||||
return call_user_func_array($controller, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
$definitions = parent::getDefinitions();
|
||||
|
||||
$count = 0;
|
||||
foreach ($definitions as &$definition) {
|
||||
if (isset($definition['weight'])) {
|
||||
// Add some micro weight.
|
||||
$definition['weight'] += ($count++) * 1e-6;
|
||||
}
|
||||
}
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLocalTasksForRoute($route_name) {
|
||||
if (!isset($this->instances[$route_name])) {
|
||||
$this->instances[$route_name] = array();
|
||||
if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $route_name)) {
|
||||
$base_routes = $cache->data['base_routes'];
|
||||
$parents = $cache->data['parents'];
|
||||
$children = $cache->data['children'];
|
||||
}
|
||||
else {
|
||||
$definitions = $this->getDefinitions();
|
||||
// We build the hierarchy by finding all tabs that should
|
||||
// appear on the current route.
|
||||
$base_routes = array();
|
||||
$parents = array();
|
||||
$children = array();
|
||||
foreach ($definitions as $plugin_id => $task_info) {
|
||||
// Fill in the base_route from the parent to insure consistency.
|
||||
if (!empty($task_info['parent_id']) && !empty($definitions[$task_info['parent_id']])) {
|
||||
$task_info['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
|
||||
// Populate the definitions we use in the next loop. Using a
|
||||
// reference like &$task_info causes bugs.
|
||||
$definitions[$plugin_id]['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
|
||||
}
|
||||
if ($route_name == $task_info['route_name']) {
|
||||
if(!empty($task_info['base_route'])) {
|
||||
$base_routes[$task_info['base_route']] = $task_info['base_route'];
|
||||
}
|
||||
// Tabs that link to the current route are viable parents
|
||||
// and their parent and children should be visible also.
|
||||
// @todo - this only works for 2 levels of tabs.
|
||||
// instead need to iterate up.
|
||||
$parents[$plugin_id] = TRUE;
|
||||
if (!empty($task_info['parent_id'])) {
|
||||
$parents[$task_info['parent_id']] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($base_routes) {
|
||||
// Find all the plugins with the same root and that are at the top
|
||||
// level or that have a visible parent.
|
||||
foreach ($definitions as $plugin_id => $task_info) {
|
||||
if (!empty($base_routes[$task_info['base_route']]) && (empty($task_info['parent_id']) || !empty($parents[$task_info['parent_id']]))) {
|
||||
// Concat '> ' with root ID for the parent of top-level tabs.
|
||||
$parent = empty($task_info['parent_id']) ? '> ' . $task_info['base_route'] : $task_info['parent_id'];
|
||||
$children[$parent][$plugin_id] = $task_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
$data = array(
|
||||
'base_routes' => $base_routes,
|
||||
'parents' => $parents,
|
||||
'children' => $children,
|
||||
);
|
||||
$this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, Cache::PERMANENT, $this->cacheTags);
|
||||
}
|
||||
// Create a plugin instance for each element of the hierarchy.
|
||||
foreach ($base_routes as $base_route) {
|
||||
// Convert the tree keyed by plugin IDs into a simple one with
|
||||
// integer depth. Create instances for each plugin along the way.
|
||||
$level = 0;
|
||||
// We used this above as the top-level parent array key.
|
||||
$next_parent = '> ' . $base_route;
|
||||
do {
|
||||
$parent = $next_parent;
|
||||
$next_parent = FALSE;
|
||||
foreach ($children[$parent] as $plugin_id => $task_info) {
|
||||
$plugin = $this->createInstance($plugin_id);
|
||||
$this->instances[$route_name][$level][$plugin_id] = $plugin;
|
||||
// Normally, _l() compares the href of every link with the current
|
||||
// path and sets the active class accordingly. But the parents of
|
||||
// the current local task may be on a different route in which
|
||||
// case we have to set the class manually by flagging it active.
|
||||
if (!empty($parents[$plugin_id]) && $route_name != $task_info['route_name']) {
|
||||
$plugin->setActive();
|
||||
}
|
||||
if (isset($children[$plugin_id])) {
|
||||
// This tab has visible children
|
||||
$next_parent = $plugin_id;
|
||||
}
|
||||
}
|
||||
$level++;
|
||||
} while ($next_parent);
|
||||
}
|
||||
|
||||
}
|
||||
return $this->instances[$route_name];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTasksBuild($current_route_name) {
|
||||
$tree = $this->getLocalTasksForRoute($current_route_name);
|
||||
$build = array();
|
||||
|
||||
// Collect all route names.
|
||||
$route_names = array();
|
||||
foreach ($tree as $instances) {
|
||||
foreach ($instances as $child) {
|
||||
$route_names[] = $child->getRouteName();
|
||||
}
|
||||
}
|
||||
// Pre-fetch all routes involved in the tree. This reduces the number
|
||||
// of SQL queries that would otherwise be triggered by the access manager.
|
||||
$routes = $route_names ? $this->routeProvider->getRoutesByNames($route_names) : array();
|
||||
|
||||
foreach ($tree as $level => $instances) {
|
||||
/** @var $instances \Drupal\Core\Menu\LocalTaskInterface[] */
|
||||
foreach ($instances as $plugin_id => $child) {
|
||||
$route_name = $child->getRouteName();
|
||||
$route_parameters = $child->getRouteParameters($this->routeMatch);
|
||||
|
||||
// Find out whether the user has access to the task.
|
||||
$access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account);
|
||||
if ($access) {
|
||||
$active = $this->isRouteActive($current_route_name, $route_name, $route_parameters);
|
||||
|
||||
// The plugin may have been set active in getLocalTasksForRoute() if
|
||||
// one of its child tabs is the active tab.
|
||||
$active = $active || $child->getActive();
|
||||
// @todo It might make sense to use link render elements instead.
|
||||
|
||||
$link = array(
|
||||
'title' => $this->getTitle($child),
|
||||
'url' => Url::fromRoute($route_name, $route_parameters),
|
||||
'localized_options' => $child->getOptions($this->routeMatch),
|
||||
);
|
||||
$build[$level][$plugin_id] = array(
|
||||
'#theme' => 'menu_local_task',
|
||||
'#link' => $link,
|
||||
'#active' => $active,
|
||||
'#weight' => $child->getWeight(),
|
||||
'#access' => $access,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the route of a certain local task is currently active.
|
||||
*
|
||||
* @param string $current_route_name
|
||||
* The route name of the current main request.
|
||||
* @param string $route_name
|
||||
* The route name of the local task to determine the active status.
|
||||
* @param array $route_parameters
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the passed route_name and route_parameters is considered
|
||||
* as the same as the one from the request, otherwise FALSE.
|
||||
*/
|
||||
protected function isRouteActive($current_route_name, $route_name, $route_parameters) {
|
||||
// Flag the list element as active if this tab's route and parameters match
|
||||
// the current request's route and route variables.
|
||||
$active = $current_route_name == $route_name;
|
||||
if ($active) {
|
||||
// The request is injected, so we need to verify that we have the expected
|
||||
// _raw_variables attribute.
|
||||
$raw_variables_bag = $this->routeMatch->getRawParameters();
|
||||
// If we don't have _raw_variables, we assume the attributes are still the
|
||||
// original values.
|
||||
$raw_variables = $raw_variables_bag ? $raw_variables_bag->all() : $this->routeMatch->getParameters()->all();
|
||||
$active = array_intersect_assoc($route_parameters, $raw_variables) == $route_parameters;
|
||||
}
|
||||
return $active;
|
||||
}
|
||||
|
||||
}
|
56
core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php
Normal file
56
core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\LocalTaskManagerInterface.
|
||||
*/
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Manages discovery and instantiation of menu local task plugins.
|
||||
*
|
||||
* This manager finds plugins that are rendered as local tasks (usually tabs).
|
||||
* Derivatives are supported for modules that wish to generate multiple tabs on
|
||||
* behalf of something else.
|
||||
*/
|
||||
interface LocalTaskManagerInterface extends PluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets the title for a local task.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\LocalTaskInterface $local_task
|
||||
* A local task plugin instance to get the title for.
|
||||
*
|
||||
* @return string
|
||||
* The localized title.
|
||||
*/
|
||||
public function getTitle(LocalTaskInterface $local_task);
|
||||
|
||||
/**
|
||||
* Find all local tasks that appear on a named route.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The route for which to find local tasks.
|
||||
*
|
||||
* @return array
|
||||
* Returns an array of task levels. Each task level contains instances
|
||||
* of local tasks (LocalTaskInterface) which appear on the tab route.
|
||||
* The array keys are the depths and the values are arrays of plugin
|
||||
* instances.
|
||||
*/
|
||||
public function getLocalTasksForRoute($route_name);
|
||||
|
||||
/**
|
||||
* Gets the render array for all local tasks.
|
||||
*
|
||||
* @param string $current_route_name
|
||||
* The route for which to make renderable local tasks.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by menu-local-tasks.html.twig.
|
||||
*/
|
||||
public function getTasksBuild($current_route_name);
|
||||
|
||||
}
|
148
core/lib/Drupal/Core/Menu/MenuActiveTrail.php
Normal file
148
core/lib/Drupal/Core/Menu/MenuActiveTrail.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuActiveTrail.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheCollector;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Provides the default implementation of the active menu trail service.
|
||||
*
|
||||
* It uses the current route name and route parameters to compare with the ones
|
||||
* of the menu links.
|
||||
*/
|
||||
class MenuActiveTrail extends CacheCollector implements MenuActiveTrailInterface {
|
||||
|
||||
/**
|
||||
* The menu link plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
|
||||
*/
|
||||
protected $menuLinkManager;
|
||||
|
||||
/**
|
||||
* The route match object for the current page.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Menu\MenuActiveTrail object.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
|
||||
* The menu link plugin manager.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* A route match object for finding the active link.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend.
|
||||
*/
|
||||
public function __construct(MenuLinkManagerInterface $menu_link_manager, RouteMatchInterface $route_match, CacheBackendInterface $cache, LockBackendInterface $lock) {
|
||||
parent::__construct(NULL, $cache, $lock);
|
||||
$this->menuLinkManager = $menu_link_manager;
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ::getActiveTrailIds()
|
||||
*/
|
||||
protected function getCid() {
|
||||
if (!isset($this->cid)) {
|
||||
$route_parameters = $this->routeMatch->getRawParameters()->all();
|
||||
ksort($route_parameters);
|
||||
return 'active-trail:route:' . $this->routeMatch->getRouteName() . ':route_parameters:' . serialize($route_parameters);
|
||||
}
|
||||
|
||||
return $this->cid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ::getActiveTrailIds()
|
||||
*/
|
||||
protected function resolveCacheMiss($menu_name) {
|
||||
$this->storage[$menu_name] = $this->doGetActiveTrailIds($menu_name);
|
||||
$this->tags[] = 'config:system.menu.' . $menu_name;
|
||||
$this->persist($menu_name);
|
||||
|
||||
return $this->storage[$menu_name];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This implementation caches all active trail IDs per route match for *all*
|
||||
* menus whose active trails are calculated on that page. This ensures 1 cache
|
||||
* get for all active trails per page load, rather than N.
|
||||
*
|
||||
* It uses the cache collector pattern to do this.
|
||||
*
|
||||
* @see ::get()
|
||||
* @see \Drupal\Core\Cache\CacheCollectorInterface
|
||||
* @see \Drupal\Core\Cache\CacheCollector
|
||||
*/
|
||||
public function getActiveTrailIds($menu_name) {
|
||||
return $this->get($menu_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for ::getActiveTrailIds().
|
||||
*/
|
||||
protected function doGetActiveTrailIds($menu_name) {
|
||||
// Parent ids; used both as key and value to ensure uniqueness.
|
||||
// We always want all the top-level links with parent == ''.
|
||||
$active_trail = array('' => '');
|
||||
|
||||
// If a link in the given menu indeed matches the route, then use it to
|
||||
// complete the active trail.
|
||||
if ($active_link = $this->getActiveLink($menu_name)) {
|
||||
if ($parents = $this->menuLinkManager->getParentIds($active_link->getPluginId())) {
|
||||
$active_trail = $parents + $active_trail;
|
||||
}
|
||||
}
|
||||
|
||||
return $active_trail;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getActiveLink($menu_name = NULL) {
|
||||
// Note: this is a very simple implementation. If you need more control
|
||||
// over the return value, such as matching a prioritized list of menu names,
|
||||
// you should substitute your own implementation for the 'menu.active_trail'
|
||||
// service in the container.
|
||||
// The menu links coming from the storage are already sorted by depth,
|
||||
// weight and ID.
|
||||
$found = NULL;
|
||||
|
||||
$route_name = $this->routeMatch->getRouteName();
|
||||
// On a default (not custom) 403 page the route name is NULL. On a custom
|
||||
// 403 page we will get the route name for that page, so we can consider
|
||||
// it a feature that a relevant menu tree may be displayed.
|
||||
if ($route_name) {
|
||||
$route_parameters = $this->routeMatch->getRawParameters()->all();
|
||||
|
||||
// Load links matching this route.
|
||||
$links = $this->menuLinkManager->loadLinksByRoute($route_name, $route_parameters, $menu_name);
|
||||
// Select the first matching link.
|
||||
if ($links) {
|
||||
$found = reset($links);
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
}
|
44
core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
Normal file
44
core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuActiveTrailInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Defines an interface for the active menu trail service.
|
||||
*
|
||||
* The active trail of a given menu is the trail from the current page to the
|
||||
* root of that menu's tree.
|
||||
*/
|
||||
interface MenuActiveTrailInterface {
|
||||
|
||||
/**
|
||||
* Gets the active trail IDs of the specified menu tree.
|
||||
*
|
||||
* @param string|NULL $menu_name
|
||||
* (optional) The menu name of the requested tree. If omitted, all menu
|
||||
* trees will be searched.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the active trail: a list of plugin IDs.
|
||||
*/
|
||||
public function getActiveTrailIds($menu_name);
|
||||
|
||||
/**
|
||||
* Fetches a menu link which matches the route name, parameters and menu name.
|
||||
*
|
||||
* @param string|NULL $menu_name
|
||||
* (optional) The menu within which to find the active link. If omitted, all
|
||||
* menus will be searched.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkInterface|NULL
|
||||
* The menu link for the given route name, parameters and menu, or NULL if
|
||||
* there is no matching menu link or the current user cannot access the
|
||||
* current page (i.e. we have a 403 response).
|
||||
*/
|
||||
public function getActiveLink($menu_name = NULL);
|
||||
|
||||
}
|
197
core/lib/Drupal/Core/Menu/MenuLinkBase.php
Normal file
197
core/lib/Drupal/Core/Menu/MenuLinkBase.php
Normal file
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuLinkBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
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 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 isEnabled() {
|
||||
return (bool) $this->pluginDefinition['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isExpanded() {
|
||||
return (bool) $this->pluginDefinition['expanded'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isResettable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTranslatable() {
|
||||
return (bool) $this->getTranslateRoute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDeletable() {
|
||||
return (bool) $this->getDeleteRoute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOptions() {
|
||||
return $this->pluginDefinition['options'] ?: array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetaData() {
|
||||
return $this->pluginDefinition['metadata'] ?: array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName() {
|
||||
return isset($this->pluginDefinition['route_name']) ? $this->pluginDefinition['route_name'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteParameters() {
|
||||
return isset($this->pluginDefinition['route_parameters']) ? $this->pluginDefinition['route_parameters'] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrlObject($title_attribute = TRUE) {
|
||||
$options = $this->getOptions();
|
||||
if ($title_attribute && $description = $this->getDescription()) {
|
||||
$options['attributes']['title'] = $description;
|
||||
}
|
||||
if (empty($this->pluginDefinition['url'])) {
|
||||
return new Url($this->pluginDefinition['route_name'], $this->pluginDefinition['route_parameters'], $options);
|
||||
}
|
||||
else {
|
||||
return Url::fromUri($this->pluginDefinition['url'], $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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(SafeMarkup::format('Menu link plugin with ID @id does not support deletion', array('@id' => $this->getPluginId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
116
core/lib/Drupal/Core/Menu/MenuLinkDefault.php
Normal file
116
core/lib/Drupal/Core/Menu/MenuLinkDefault.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?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,
|
||||
'enabled' => 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 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 getDescription() {
|
||||
if ($this->pluginDefinition['description']) {
|
||||
return $this->t($this->pluginDefinition['description']);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isResettable() {
|
||||
// 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);
|
||||
// Update the definition.
|
||||
$this->pluginDefinition = $overrides + $this->getPluginDefinition();
|
||||
if ($persist) {
|
||||
// Always save the menu name as an override to avoid defaulting to tools.
|
||||
$overrides['menu_name'] = $this->pluginDefinition['menu_name'];
|
||||
$this->staticOverride->saveOverride($this->getPluginId(), $overrides);
|
||||
}
|
||||
return $this->pluginDefinition;
|
||||
}
|
||||
|
||||
}
|
236
core/lib/Drupal/Core/Menu/MenuLinkInterface.php
Normal file
236
core/lib/Drupal/Core/Menu/MenuLinkInterface.php
Normal file
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuLinkInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for classes providing a type of menu link.
|
||||
*/
|
||||
interface MenuLinkInterface extends PluginInspectionInterface, DerivativeInspectionInterface, CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* 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 enabled (not hidden).
|
||||
*
|
||||
* @return bool
|
||||
* TRUE for enabled, FALSE otherwise.
|
||||
*/
|
||||
public function isEnabled();
|
||||
|
||||
/**
|
||||
* 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 isResettable();
|
||||
|
||||
/**
|
||||
* 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 the route name, if available.
|
||||
*
|
||||
* @return string
|
||||
* The name of the route this menu link links to.
|
||||
*/
|
||||
public function getRouteName();
|
||||
|
||||
/**
|
||||
* Returns the route parameters, if available.
|
||||
*
|
||||
* @return array
|
||||
* An array of parameter names and values.
|
||||
*/
|
||||
public function getRouteParameters();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* An associative array of options.
|
||||
*/
|
||||
public function getOptions();
|
||||
|
||||
/**
|
||||
* Returns any metadata for this link.
|
||||
*
|
||||
* @return array
|
||||
* The metadata for the menu link.
|
||||
*/
|
||||
public function getMetaData();
|
||||
|
||||
/**
|
||||
* 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 \Drupal\Core\Url|null
|
||||
* A Url object, 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 \Drupal\Core\Url|null
|
||||
* A Url object, 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 \Drupal\Core\Url|null
|
||||
* A Url object, or NULL if there is no route (e.g. when the link is not
|
||||
* translatable).
|
||||
*/
|
||||
public function getTranslateRoute();
|
||||
|
||||
}
|
422
core/lib/Drupal/Core/Menu/MenuLinkManager.php
Normal file
422
core/lib/Drupal/Core/Menu/MenuLinkManager.php
Normal file
|
@ -0,0 +1,422 @@
|
|||
<?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\SafeMarkup;
|
||||
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,
|
||||
'enabled' => 1,
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
$this->discovery = new YamlDiscovery('links.menu', $this->moduleHandler->getModuleDirectories());
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($this->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 = array()) {
|
||||
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(array('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(SafeMarkup::format('Menu link plugin with ID @id does not support deletion', array('@id' => $id)));
|
||||
}
|
||||
$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 = array(), $menu_name = NULL) {
|
||||
$instances = array();
|
||||
$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) || $id === '') {
|
||||
throw new PluginException(SafeMarkup::format('The ID @id already exists as a plugin definition or is not valid', array('@id' => $id)));
|
||||
}
|
||||
// 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(SafeMarkup::format('Menu link %id is not resettable', array('%id' => $id)));
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
|
||||
}
|
200
core/lib/Drupal/Core/Menu/MenuLinkManagerInterface.php
Normal file
200
core/lib/Drupal/Core/Menu/MenuLinkManagerInterface.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?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.
|
||||
*
|
||||
* @param string $id
|
||||
* The menu link plugin ID.
|
||||
*
|
||||
* @return array
|
||||
* An unordered array of IDs representing the IDs of all children, or NULL
|
||||
* if the ID is invalid.
|
||||
*/
|
||||
public function getChildIds($id);
|
||||
|
||||
/**
|
||||
* Determines if any links use a given menu name.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu name.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if any links are present in the named menu, FALSE otherwise.
|
||||
*/
|
||||
public function menuNameInUse($menu_name);
|
||||
|
||||
/**
|
||||
* Resets any local definition cache. Used for testing.
|
||||
*/
|
||||
public function resetDefinitions();
|
||||
|
||||
}
|
302
core/lib/Drupal/Core/Menu/MenuLinkTree.php
Normal file
302
core/lib/Drupal/Core/Menu/MenuLinkTree.php
Normal file
|
@ -0,0 +1,302 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuLinkTree.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
|
||||
/**
|
||||
* Implements the loading, transforming and rendering of menu link trees.
|
||||
*/
|
||||
class MenuLinkTree implements MenuLinkTreeInterface {
|
||||
|
||||
/**
|
||||
* The menu link tree storage.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuTreeStorageInterface
|
||||
*/
|
||||
protected $treeStorage;
|
||||
|
||||
/**
|
||||
* The route provider to load routes by name.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The active menu trail service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuActiveTrailInterface
|
||||
*/
|
||||
protected $menuActiveTrail;
|
||||
|
||||
/**
|
||||
* The controller resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Menu\MenuLinkTree object.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuTreeStorageInterface $tree_storage
|
||||
* The menu link tree storage.
|
||||
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
|
||||
* The menu link plugin manager.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider to load routes by name.
|
||||
* @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
|
||||
* The active menu trail service.
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
*/
|
||||
public function __construct(MenuTreeStorageInterface $tree_storage, MenuLinkManagerInterface $menu_link_manager, RouteProviderInterface $route_provider, MenuActiveTrailInterface $menu_active_trail, ControllerResolverInterface $controller_resolver) {
|
||||
$this->treeStorage = $tree_storage;
|
||||
$this->menuLinkManager = $menu_link_manager;
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->menuActiveTrail = $menu_active_trail;
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCurrentRouteMenuTreeParameters($menu_name) {
|
||||
$active_trail = $this->menuActiveTrail->getActiveTrailIds($menu_name);
|
||||
|
||||
$parameters = new MenuTreeParameters();
|
||||
$parameters->setActiveTrail($active_trail)
|
||||
// We want links in the active trail to be expanded.
|
||||
->addExpandedParents($active_trail)
|
||||
// We marked the links in the active trail to be expanded, but we also
|
||||
// want their descendants that have the "expanded" flag enabled to be
|
||||
// expanded.
|
||||
->addExpandedParents($this->treeStorage->getExpanded($menu_name, $active_trail));
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($menu_name, MenuTreeParameters $parameters) {
|
||||
$data = $this->treeStorage->loadTreeData($menu_name, $parameters);
|
||||
// Pre-load all the route objects in the tree for access checks.
|
||||
if ($data['route_names']) {
|
||||
$this->routeProvider->getRoutesByNames($data['route_names']);
|
||||
}
|
||||
return $this->createInstances($data['tree']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tree containing of MenuLinkTreeElement based upon tree data.
|
||||
*
|
||||
* This method converts the tree representation as array coming from the tree
|
||||
* storage to a tree containing a list of MenuLinkTreeElement[].
|
||||
*
|
||||
* @param array $data_tree
|
||||
* The tree data coming from the menu tree storage.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* An array containing the elements of a menu tree.
|
||||
*/
|
||||
protected function createInstances(array $data_tree) {
|
||||
$tree = array();
|
||||
foreach ($data_tree as $key => $element) {
|
||||
$subtree = $this->createInstances($element['subtree']);
|
||||
// Build a MenuLinkTreeElement out of the menu tree link definition:
|
||||
// transform the tree link definition into a link definition and store
|
||||
// tree metadata.
|
||||
$tree[$key] = new MenuLinkTreeElement(
|
||||
$this->menuLinkManager->createInstance($element['definition']['id']),
|
||||
(bool) $element['has_children'],
|
||||
(int) $element['depth'],
|
||||
(bool) $element['in_active_trail'],
|
||||
$subtree
|
||||
);
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform(array $tree, array $manipulators) {
|
||||
foreach ($manipulators as $manipulator) {
|
||||
$callable = $manipulator['callable'];
|
||||
$callable = $this->controllerResolver->getControllerFromDefinition($callable);
|
||||
// Prepare the arguments for the menu tree manipulator callable; the first
|
||||
// argument is always the menu link tree.
|
||||
if (isset($manipulator['args'])) {
|
||||
array_unshift($manipulator['args'], $tree);
|
||||
$tree = call_user_func_array($callable, $manipulator['args']);
|
||||
}
|
||||
else {
|
||||
$tree = call_user_func($callable, $tree);
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(array $tree) {
|
||||
$tree_access_cacheability = new CacheableMetadata();
|
||||
$tree_link_cacheability = new CacheableMetadata();
|
||||
$items = $this->buildItems($tree, $tree_access_cacheability, $tree_link_cacheability);
|
||||
|
||||
$build = [];
|
||||
|
||||
// Apply the tree-wide gathered access cacheability metadata and link
|
||||
// cacheability metadata to the render array. This ensures that the
|
||||
// rendered menu is varied by the cache contexts that the access results
|
||||
// and (dynamic) links depended upon, and invalidated by the cache tags
|
||||
// that may change the values of the access results and links.
|
||||
$tree_cacheability = $tree_access_cacheability->merge($tree_link_cacheability);
|
||||
$tree_cacheability->applyTo($build);
|
||||
|
||||
if ($items) {
|
||||
// Make sure drupal_render() does not re-order the links.
|
||||
$build['#sorted'] = TRUE;
|
||||
// Get the menu name from the last link.
|
||||
$item = end($items);
|
||||
$link = $item['original_link'];
|
||||
$menu_name = $link->getMenuName();
|
||||
// Add the theme wrapper for outer markup.
|
||||
// Allow menu-specific theme overrides.
|
||||
$build['#theme'] = 'menu__' . strtr($menu_name, '-', '_');
|
||||
$build['#items'] = $items;
|
||||
// Set cache tag.
|
||||
$build['#cache']['tags'][] = 'config:system.menu.' . $menu_name;
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the #items property for a menu tree's renderable array.
|
||||
*
|
||||
* Helper function for ::build().
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* A data structure representing the tree, as returned from
|
||||
* MenuLinkTreeInterface::load().
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata &$tree_access_cacheability
|
||||
* Internal use only. The aggregated cacheability metadata for the access
|
||||
* results across the entire tree. Used when rendering the root level.
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata &$tree_link_cacheability
|
||||
* Internal use only. The aggregated cacheability metadata for the menu
|
||||
* links across the entire tree. Used when rendering the root level.
|
||||
*
|
||||
* @return array
|
||||
* The value to use for the #items property of a renderable menu.
|
||||
*
|
||||
* @throws \DomainException
|
||||
*/
|
||||
protected function buildItems(array $tree, CacheableMetadata &$tree_access_cacheability, CacheableMetadata &$tree_link_cacheability) {
|
||||
$items = array();
|
||||
|
||||
foreach ($tree as $data) {
|
||||
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
|
||||
$link = $data->link;
|
||||
// Generally we only deal with visible links, but just in case.
|
||||
if (!$link->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($data->access !== NULL && !$data->access instanceof AccessResultInterface) {
|
||||
throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.');
|
||||
}
|
||||
|
||||
// Gather the access cacheability of every item in the menu link tree,
|
||||
// including inaccessible items. This allows us to render cache the menu
|
||||
// tree, yet still automatically vary the rendered menu by the same cache
|
||||
// contexts that the access results vary by.
|
||||
// However, if $data->access is not an AccessResultInterface object, this
|
||||
// will still render the menu link, because this method does not want to
|
||||
// require access checking to be able to render a menu tree.
|
||||
if ($data->access instanceof AccessResultInterface) {
|
||||
$tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($data->access));
|
||||
}
|
||||
|
||||
// Gather the cacheability of every item in the menu link tree. Some links
|
||||
// may be dynamic: they may have a dynamic text (e.g. a "Hi, <user>" link
|
||||
// text, which would vary by 'user' cache context), or a dynamic route
|
||||
// name or route parameters.
|
||||
$tree_link_cacheability = $tree_link_cacheability->merge(CacheableMetadata::createFromObject($data->link));
|
||||
|
||||
// Only render accessible links.
|
||||
if ($data->access instanceof AccessResultInterface && !$data->access->isAllowed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = ['menu-item'];
|
||||
// Set a class for the <li>-tag. Only set 'expanded' class if the link
|
||||
// also has visible children within the current tree.
|
||||
if ($data->hasChildren && !empty($data->subtree)) {
|
||||
$class[] = 'menu-item--expanded';
|
||||
}
|
||||
elseif ($data->hasChildren) {
|
||||
$class[] = 'menu-item--collapsed';
|
||||
}
|
||||
// Set a class if the link is in the active trail.
|
||||
if ($data->inActiveTrail) {
|
||||
$class[] = 'menu-item--active-trail';
|
||||
}
|
||||
|
||||
// Note: links are rendered in the menu.html.twig template; and they
|
||||
// automatically bubble their associated cacheability metadata.
|
||||
$element = array();
|
||||
$element['attributes'] = new Attribute();
|
||||
$element['attributes']['class'] = $class;
|
||||
$element['title'] = $link->getTitle();
|
||||
$element['url'] = $link->getUrlObject();
|
||||
$element['url']->setOption('set_active_class', TRUE);
|
||||
$element['below'] = $data->subtree ? $this->buildItems($data->subtree, $tree_access_cacheability, $tree_link_cacheability) : array();
|
||||
if (isset($data->options)) {
|
||||
$element['url']->setOptions(NestedArray::mergeDeep($element['url']->getOptions(), $data->options));
|
||||
}
|
||||
$element['original_link'] = $link;
|
||||
// Index using the link's unique ID.
|
||||
$items[$link->getPluginId()] = $element;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function maxDepth() {
|
||||
return $this->treeStorage->maxDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSubtreeHeight($id) {
|
||||
return $this->treeStorage->getSubtreeHeight($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExpanded($menu_name, array $parents) {
|
||||
return $this->treeStorage->getExpanded($menu_name, $parents);
|
||||
}
|
||||
|
||||
}
|
128
core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php
Normal file
128
core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuLinkTreeElement.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Provides a value object to model an element in a menu link tree.
|
||||
*
|
||||
* \Drupal\Core\Menu\MenuLinkTreeElement objects represent a menu link's data.
|
||||
* Objects of this class provide complimentary data: the placement in a tree.
|
||||
* Therefore, we can summarize this split as follows:
|
||||
* - Menu link objects contain all information about an individual menu link,
|
||||
* plus what their parent is. But they don't know where exactly in a menu link
|
||||
* tree they live.
|
||||
* - Instances of this class are complimentary to those objects, they know:
|
||||
* - All additional metadata from {menu_tree}, which contains "materialized"
|
||||
* metadata about a menu link tree, such as whether a link in the tree has
|
||||
* visible children and the depth relative to the root.
|
||||
* - Plus all additional metadata that's adjusted for the current tree query,
|
||||
* such as whether the link is in the active trail, whether the link is
|
||||
* accessible for the current user, and the link's children (which are only
|
||||
* loaded if the link was marked as "expanded" by the query).
|
||||
*
|
||||
* @see \Drupal\Core\Menu\MenuTreeStorage::loadTreeData()
|
||||
*/
|
||||
class MenuLinkTreeElement {
|
||||
|
||||
/**
|
||||
* The menu link for this element in a menu link tree.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkInterface
|
||||
*/
|
||||
public $link;
|
||||
|
||||
/**
|
||||
* The subtree of this element in the menu link tree (this link's children).
|
||||
*
|
||||
* (Children of a link are only loaded if a link is marked as "expanded" by
|
||||
* the query.)
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
*/
|
||||
public $subtree;
|
||||
|
||||
/**
|
||||
* The depth of this link relative to the root of the tree.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $depth;
|
||||
|
||||
/**
|
||||
* Whether this link has any children at all.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $hasChildren;
|
||||
|
||||
/**
|
||||
* Whether this link is in the active trail.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $inActiveTrail;
|
||||
|
||||
/**
|
||||
* Whether this link is accessible by the current user.
|
||||
*
|
||||
* If the value is NULL the access was not determined yet, if an access result
|
||||
* object, it was determined already.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessResultInterface|NULL
|
||||
*/
|
||||
public $access;
|
||||
|
||||
/**
|
||||
* Additional options for this link.
|
||||
*
|
||||
* This is merged (\Drupal\Component\Utility\NestedArray::mergeDeep()) with
|
||||
* \Drupal\Core\Menu\MenuLinkInterface::getOptions(), to allow menu link tree
|
||||
* manipulators to add or override link options.
|
||||
*/
|
||||
public $options = array();
|
||||
|
||||
/**
|
||||
* Constructs a new \Drupal\Core\Menu\MenuLinkTreeElement.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkInterface $link
|
||||
* The menu link for this element in the menu link tree.
|
||||
* @param bool $has_children
|
||||
* A flag as to whether this element has children even if they are not
|
||||
* included in the tree (i.e. this may be TRUE even if $subtree is empty).
|
||||
* @param int $depth
|
||||
* The depth of this element relative to the tree root.
|
||||
* @param bool $in_active_trail
|
||||
* A flag as to whether this link was included in the list of active trail
|
||||
* IDs used to build the tree.
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $subtree
|
||||
* The children of this element in the menu link tree.
|
||||
*/
|
||||
public function __construct(MenuLinkInterface $link, $has_children, $depth, $in_active_trail, array $subtree) {
|
||||
// Essential properties.
|
||||
$this->link = $link;
|
||||
$this->hasChildren = $has_children;
|
||||
$this->depth = $depth;
|
||||
$this->subtree = $subtree;
|
||||
$this->inActiveTrail = $in_active_trail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts all menu links in the current subtree.
|
||||
*
|
||||
* @return int
|
||||
* The number of menu links in this subtree (one plus the number of menu
|
||||
* links in all descendants).
|
||||
*/
|
||||
public function count() {
|
||||
$sum = function ($carry, MenuLinkTreeElement $element) {
|
||||
return $carry + $element->count();
|
||||
};
|
||||
return 1 + array_reduce($this->subtree, $sum);
|
||||
}
|
||||
|
||||
}
|
134
core/lib/Drupal/Core/Menu/MenuLinkTreeInterface.php
Normal file
134
core/lib/Drupal/Core/Menu/MenuLinkTreeInterface.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuLinkTreeInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Defines an interface for loading, transforming and rendering menu link trees.
|
||||
*
|
||||
* The main purposes of this interface are:
|
||||
* - Load a list of menu links, given a menu name, using
|
||||
* MenuLinkTreeInterface::load(). Loaded menu links are returned as a
|
||||
* tree by looking at the links' tree meta-data.
|
||||
* - Which links are loaded can be specified in the menu link tree parameters
|
||||
* that are passed to the load() method. You can build your own set of
|
||||
* parameters, or you can start from typical defaults by calling the
|
||||
* MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters() method. See
|
||||
* \Drupal\Core\Menu\MenuTreeParameters for more on menu tree parameters.
|
||||
* - Transform a menu link tree, by calling MenuLinkTreeInterface::transform().
|
||||
* Examples include access checking, adding custom classes, extracting a
|
||||
* subtree depending on the active trail, etc. Note that translation is not
|
||||
* a tree transformation, because menu links themselves are responsible
|
||||
* for translation. Transformations are performed by "menu link tree
|
||||
* manipulators", which are functions or methods; see
|
||||
* \Drupal\menu_link\DefaultMenuTreeManipulators for examples.
|
||||
* - Create a render array using MenuLinkTreeInterface::build().
|
||||
*/
|
||||
interface MenuLinkTreeInterface {
|
||||
|
||||
/**
|
||||
* Gets the link tree parameters for rendering a specific menu.
|
||||
*
|
||||
* Builds menu link tree parameters that:
|
||||
* - Expand all links in the active trail based on route being viewed.
|
||||
* - Expand the descendents of the links in the active trail whose
|
||||
* 'expanded' flag is enabled.
|
||||
*
|
||||
* This only sets the (relatively complex) parameters to achieve the two above
|
||||
* goals, but you can still further customize these parameters.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu name, needed for retrieving the active trail and links with the
|
||||
* 'expanded' flag enabled.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuTreeParameters
|
||||
* The parameters to determine which menu links to be loaded into a tree.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\MenuTreeParameters
|
||||
*/
|
||||
public function getCurrentRouteMenuTreeParameters($menu_name);
|
||||
|
||||
/**
|
||||
* Loads a menu tree with a menu link plugin instance at each element.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The name of the menu.
|
||||
* @param \Drupal\Core\Menu\MenuTreeParameters $parameters
|
||||
* The parameters to determine which menu links to be loaded into a tree.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* A menu link tree.
|
||||
*/
|
||||
public function load($menu_name, MenuTreeParameters $parameters);
|
||||
|
||||
/**
|
||||
* Applies menu link tree manipulators to transform the given tree.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* The menu tree to manipulate.
|
||||
* @param array $manipulators
|
||||
* The menu link tree manipulators to apply. Each is an array with keys:
|
||||
* - callable: a callable or a string that can be resolved to a callable
|
||||
* by \Drupal\Core\Controller\ControllerResolverInterface::getControllerFromDefinition()
|
||||
* - args: optional array of arguments to pass to the callable after $tree.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* The manipulated menu link tree.
|
||||
*/
|
||||
public function transform(array $tree, array $manipulators);
|
||||
|
||||
/**
|
||||
* Builds a renderable array from a menu tree.
|
||||
*
|
||||
* The menu item's LI element is given one of the following classes:
|
||||
* - expanded: The menu item is showing its submenu.
|
||||
* - collapsed: The menu item has a submenu that is not shown.
|
||||
* - leaf: The menu item has no submenu.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* A data structure representing the tree, as returned from
|
||||
* MenuLinkTreeInterface::load().
|
||||
*
|
||||
* @return array
|
||||
* A renderable array.
|
||||
*/
|
||||
public function build(array $tree);
|
||||
|
||||
/**
|
||||
* Returns the maximum depth of tree that is supported.
|
||||
*
|
||||
* @return int
|
||||
* The maximum depth.
|
||||
*/
|
||||
public function maxDepth();
|
||||
|
||||
/**
|
||||
* Finds the height of a subtree rooted by of the given ID.
|
||||
*
|
||||
* @param string $id
|
||||
* The ID of an item in the storage.
|
||||
*
|
||||
* @return int
|
||||
* Returns the height of the subtree. This will be at least 1 if the ID
|
||||
* exists, or 0 if the ID does not exist in the storage.
|
||||
*/
|
||||
public function getSubtreeHeight($id);
|
||||
|
||||
/**
|
||||
* Finds expanded links in a menu given a set of possible parents.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu name.
|
||||
* @param array $parents
|
||||
* One or more parent IDs to match.
|
||||
*
|
||||
* @return array
|
||||
* The menu link IDs that are flagged as expanded in this menu.
|
||||
*/
|
||||
public function getExpanded($menu_name, array $parents);
|
||||
|
||||
}
|
199
core/lib/Drupal/Core/Menu/MenuParentFormSelector.php
Normal file
199
core/lib/Drupal/Core/Menu/MenuParentFormSelector.php
Normal file
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuParentFormSelector.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
|
||||
/**
|
||||
* Default implementation of the menu parent form selector service.
|
||||
*
|
||||
* The form selector is a list of all appropriate menu links.
|
||||
*/
|
||||
class MenuParentFormSelector implements MenuParentFormSelectorInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The menu link tree service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
|
||||
*/
|
||||
protected $menuLinkTree;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Menu\MenuParentFormSelector
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree
|
||||
* The menu link tree service.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
*/
|
||||
public function __construct(MenuLinkTreeInterface $menu_link_tree, EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
|
||||
$this->menuLinkTree = $menu_link_tree;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParentSelectOptions($id = '', array $menus = NULL, CacheableMetadata &$cacheability = NULL) {
|
||||
if (!isset($menus)) {
|
||||
$menus = $this->getMenuOptions();
|
||||
}
|
||||
|
||||
$options = array();
|
||||
$depth_limit = $this->getParentDepthLimit($id);
|
||||
foreach ($menus as $menu_name => $menu_title) {
|
||||
$options[$menu_name . ':'] = '<' . $menu_title . '>';
|
||||
|
||||
$parameters = new MenuTreeParameters();
|
||||
$parameters->setMaxDepth($depth_limit);
|
||||
$tree = $this->menuLinkTree->load($menu_name, $parameters);
|
||||
$manipulators = array(
|
||||
array('callable' => 'menu.default_tree_manipulators:checkNodeAccess'),
|
||||
array('callable' => 'menu.default_tree_manipulators:checkAccess'),
|
||||
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
|
||||
);
|
||||
$tree = $this->menuLinkTree->transform($tree, $manipulators);
|
||||
$this->parentSelectOptionsTreeWalk($tree, $menu_name, '--', $options, $id, $depth_limit, $cacheability);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parentSelectElement($menu_parent, $id = '', array $menus = NULL) {
|
||||
$options_cacheability = new CacheableMetadata();
|
||||
$options = $this->getParentSelectOptions($id, $menus, $options_cacheability);
|
||||
// If no options were found, there is nothing to select.
|
||||
if ($options) {
|
||||
$element = array(
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
);
|
||||
if (!isset($options[$menu_parent])) {
|
||||
// The requested menu parent cannot be found in the menu anymore. Try
|
||||
// setting it to the top level in the current menu.
|
||||
list($menu_name, $parent) = explode(':', $menu_parent, 2);
|
||||
$menu_parent = $menu_name . ':';
|
||||
}
|
||||
if (isset($options[$menu_parent])) {
|
||||
// Only provide the default value if it is valid among the options.
|
||||
$element += array('#default_value' => $menu_parent);
|
||||
}
|
||||
$options_cacheability->applyTo($element);
|
||||
return $element;
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum depth of the possible parents of the menu link.
|
||||
*
|
||||
* @param string $id
|
||||
* The menu link plugin ID or an empty value for a new link.
|
||||
*
|
||||
* @return int
|
||||
* The depth related to the depth of the given menu link.
|
||||
*/
|
||||
protected function getParentDepthLimit($id) {
|
||||
if ($id) {
|
||||
$limit = $this->menuLinkTree->maxDepth() - $this->menuLinkTree->getSubtreeHeight($id);
|
||||
}
|
||||
else {
|
||||
$limit = $this->menuLinkTree->maxDepth() - 1;
|
||||
}
|
||||
return $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over all items in the tree to prepare the parents select options.
|
||||
*
|
||||
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
||||
* The menu tree.
|
||||
* @param string $menu_name
|
||||
* The menu name.
|
||||
* @param string $indent
|
||||
* The indentation string used for the label.
|
||||
* @param array $options
|
||||
* The select options.
|
||||
* @param string $exclude
|
||||
* An excluded menu link.
|
||||
* @param int $depth_limit
|
||||
* The maximum depth of menu links considered for the select options.
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata|NULL &$cacheability
|
||||
* The object to add cacheability metadata to, if not NULL.
|
||||
*/
|
||||
protected function parentSelectOptionsTreeWalk(array $tree, $menu_name, $indent, array &$options, $exclude, $depth_limit, CacheableMetadata &$cacheability = NULL) {
|
||||
foreach ($tree as $element) {
|
||||
if ($element->depth > $depth_limit) {
|
||||
// Don't iterate through any links on this level.
|
||||
break;
|
||||
}
|
||||
|
||||
// Collect the cacheability metadata of the access result, as well as the
|
||||
// link.
|
||||
if ($cacheability) {
|
||||
$cacheability = $cacheability
|
||||
->merge(CacheableMetadata::createFromObject($element->access))
|
||||
->merge(CacheableMetadata::createFromObject($element->link));
|
||||
}
|
||||
|
||||
// Only show accessible links.
|
||||
if (!$element->access->isAllowed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$link = $element->link;
|
||||
if ($link->getPluginId() != $exclude) {
|
||||
$title = $indent . ' ' . Unicode::truncate($link->getTitle(), 30, TRUE, FALSE);
|
||||
if (!$link->isEnabled()) {
|
||||
$title .= ' (' . $this->t('disabled') . ')';
|
||||
}
|
||||
$options[$menu_name . ':' . $link->getPluginId()] = $title;
|
||||
if (!empty($element->subtree)) {
|
||||
$this->parentSelectOptionsTreeWalk($element->subtree, $menu_name, $indent . '--', $options, $exclude, $depth_limit, $cacheability);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of menu names for use as options.
|
||||
*
|
||||
* @param array $menu_names
|
||||
* (optional) Array of menu names to limit the options, or NULL to load all.
|
||||
*
|
||||
* @return array
|
||||
* Keys are menu names (ids) values are the menu labels.
|
||||
*/
|
||||
protected function getMenuOptions(array $menu_names = NULL) {
|
||||
$menus = $this->entityManager->getStorage('menu')->loadMultiple($menu_names);
|
||||
$options = array();
|
||||
/** @var \Drupal\system\MenuInterface[] $menus */
|
||||
foreach ($menus as $menu) {
|
||||
$options[$menu->id()] = $menu->label();
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuParentFormSelectorInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
|
||||
/**
|
||||
* Defines an interface for menu selector form elements and menu link options.
|
||||
*/
|
||||
interface MenuParentFormSelectorInterface {
|
||||
|
||||
/**
|
||||
* Gets the options for a select element to choose a menu and parent.
|
||||
*
|
||||
* @param string $id
|
||||
* Optional ID of a link plugin. This will exclude the link and its
|
||||
* children from the select options.
|
||||
* @param array $menus
|
||||
* Optional array of menu names as keys and titles as values to limit
|
||||
* the select options. If NULL, all menus will be included.
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata|NULL &$cacheability
|
||||
* Optional cacheability metadata object, which will be populated based on
|
||||
* the accessibility of the links and the cacheability of the links.
|
||||
*
|
||||
* @return array
|
||||
* Keyed array where the keys are contain a menu name and parent ID and
|
||||
* the values are a menu name or link title indented by depth.
|
||||
*/
|
||||
public function getParentSelectOptions($id = '', array $menus = NULL, CacheableMetadata &$cacheability = NULL);
|
||||
|
||||
/**
|
||||
* Gets a form element to choose a menu and parent.
|
||||
*
|
||||
* The specific type of form element will vary depending on the
|
||||
* implementation, but callers will normally need to set the #title for the
|
||||
* element.
|
||||
*
|
||||
* @param string $menu_parent
|
||||
* A menu name and parent ID concatenated with a ':' character to use as the
|
||||
* default value.
|
||||
* @param string $id
|
||||
* (optional) ID of a link plugin. This will exclude the link and its
|
||||
* children from being selected.
|
||||
* @param array $menus
|
||||
* (optional) array of menu names as keys and titles as values to limit
|
||||
* the values that may be selected. If NULL, all menus will be included.
|
||||
*
|
||||
* @return array
|
||||
* A form element to choose a parent, or an empty array if no possible
|
||||
* parents exist for the given parameters. The resulting form value will be
|
||||
* a single string containing the chosen menu name and parent ID separated
|
||||
* by a ':' character.
|
||||
*/
|
||||
public function parentSelectElement($menu_parent, $id = '', array $menus = NULL);
|
||||
|
||||
}
|
223
core/lib/Drupal/Core/Menu/MenuTreeParameters.php
Normal file
223
core/lib/Drupal/Core/Menu/MenuTreeParameters.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuTreeParameters.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Provides a value object to model menu tree parameters.
|
||||
*
|
||||
* Menu tree parameters are used to determine the set of definitions to be
|
||||
* loaded from \Drupal\Core\Menu\MenuTreeStorageInterface. Hence they determine
|
||||
* the shape and content of the tree:
|
||||
* - Which parent IDs should be used to restrict the tree. Only links with
|
||||
* a parent in the list will be included.
|
||||
* - Which menu links are omitted, depending on the minimum and maximum depth.
|
||||
*
|
||||
* @todo Add getter methods and make all properties protected and define an
|
||||
* interface instead of using the concrete class to type hint.
|
||||
* https://www.drupal.org/node/2302041
|
||||
*/
|
||||
class MenuTreeParameters {
|
||||
|
||||
/**
|
||||
* A menu link plugin ID that should be used as the root.
|
||||
*
|
||||
* By default the root ID of empty string '' is used. However, when only the
|
||||
* descendants (subtree) of a certain menu link are needed, a custom root can
|
||||
* be specified.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $root = '';
|
||||
|
||||
/**
|
||||
* The minimum depth of menu links in the resulting tree relative to the root.
|
||||
*
|
||||
* Defaults to 1, which is the default to build a whole tree for a menu
|
||||
* (excluding the root).
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $minDepth = NULL;
|
||||
|
||||
/**
|
||||
* The maximum depth of menu links in the resulting tree relative to the root.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $maxDepth = NULL;
|
||||
|
||||
/**
|
||||
* An array of parent link IDs.
|
||||
*
|
||||
* This restricts the tree to only menu links that are at the top level or
|
||||
* have a parent ID in this list. If empty, the whole menu tree is built.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $expandedParents = array();
|
||||
|
||||
/**
|
||||
* The IDs from the currently active menu link to the root of the whole tree.
|
||||
*
|
||||
* This is an array of menu link plugin IDs, representing the trail from the
|
||||
* currently active menu link to the ("real") root of that menu link's menu.
|
||||
* This does not affect the way the tree is built. It is only used to set the
|
||||
* value of the inActiveTrail property for each tree element.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $activeTrail = array();
|
||||
|
||||
/**
|
||||
* The conditions used to restrict which links are loaded.
|
||||
*
|
||||
* An associative array of custom query condition key/value pairs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $conditions = array();
|
||||
|
||||
/**
|
||||
* Sets a root for menu tree loading.
|
||||
*
|
||||
* @param string $root
|
||||
* A menu link plugin ID, or empty string '' to use the root of the whole
|
||||
* tree.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function setRoot($root) {
|
||||
$this->root = (string) $root;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a minimum depth for menu tree loading.
|
||||
*
|
||||
* @param int $min_depth
|
||||
* The (root-relative) minimum depth to apply.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMinDepth($min_depth) {
|
||||
$this->minDepth = max(1, $min_depth);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a maximum depth for menu tree loading.
|
||||
*
|
||||
* @param int $max_depth
|
||||
* The (root-relative) maximum depth to apply.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function setMaxDepth($max_depth) {
|
||||
$this->maxDepth = $max_depth;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds parent menu links IDs to restrict the tree.
|
||||
*
|
||||
* @param string[] $parents
|
||||
* An array containing parent IDs. If supplied, the tree is limited to
|
||||
* links that have these parents.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addExpandedParents(array $parents) {
|
||||
$this->expandedParents = array_merge($this->expandedParents, $parents);
|
||||
$this->expandedParents = array_unique($this->expandedParents);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active trail IDs used to set the inActiveTrail property.
|
||||
*
|
||||
* @param string[] $active_trail
|
||||
* An array containing the active trail: a list of menu link plugin IDs.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @see \Drupal\Core\Menu\MenuActiveTrail::getActiveTrailIds()
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function setActiveTrail(array $active_trail) {
|
||||
$this->activeTrail = $active_trail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom query condition.
|
||||
*
|
||||
* @param string $definition_field
|
||||
* Only conditions that are testing menu link definition fields are allowed.
|
||||
* @param mixed $value
|
||||
* The value to test the link definition field against. In most cases, this
|
||||
* is a scalar. For more complex options, it is an array. The meaning of
|
||||
* each element in the array is dependent on the $operator.
|
||||
* @param string|null $operator
|
||||
* (optional) The comparison operator, such as =, <, or >=. It also accepts
|
||||
* more complex options such as IN, LIKE, or BETWEEN. If NULL, defaults to
|
||||
* the = operator.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCondition($definition_field, $value, $operator = NULL) {
|
||||
if (!isset($operator)) {
|
||||
$this->conditions[$definition_field] = $value;
|
||||
}
|
||||
else {
|
||||
$this->conditions[$definition_field] = array($value, $operator);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes links that are not enabled.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function onlyEnabledLinks() {
|
||||
$this->addCondition('enabled', 1);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures only the top level of the tree is loaded.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTopLevelOnly() {
|
||||
$this->setMaxDepth(1);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes the root menu link from the tree.
|
||||
*
|
||||
* Note that this is only necessary when you specified a custom root, because
|
||||
* the normal root ID is the empty string, '', which does not correspond to an
|
||||
* actual menu link. Hence when loading a menu link tree without specifying a
|
||||
* custom root the tree will start at the children even if this method has not
|
||||
* been called.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function excludeRoot() {
|
||||
$this->setMinDepth(1);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
1477
core/lib/Drupal/Core/Menu/MenuTreeStorage.php
Normal file
1477
core/lib/Drupal/Core/Menu/MenuTreeStorage.php
Normal file
File diff suppressed because it is too large
Load diff
281
core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php
Normal file
281
core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php
Normal file
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\MenuTreeStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Defines an interface for storing a menu tree containing menu link IDs.
|
||||
*
|
||||
* The tree contains a hierarchy of menu links which have an ID as well as a
|
||||
* route name or external URL.
|
||||
*/
|
||||
interface MenuTreeStorageInterface {
|
||||
|
||||
/**
|
||||
* The maximum depth of tree the storage implementation supports.
|
||||
*
|
||||
* @return int
|
||||
* The maximum depth.
|
||||
*/
|
||||
public function maxDepth();
|
||||
|
||||
/**
|
||||
* Clears all definitions cached in memory.
|
||||
*/
|
||||
public function resetDefinitions();
|
||||
|
||||
/**
|
||||
* Rebuilds the stored menu link definitions.
|
||||
*
|
||||
* Links that saved by passing definitions into this method must be included
|
||||
* on all future calls, or they will be purged. This allows for automatic
|
||||
* cleanup e.g. when modules are uninstalled.
|
||||
*
|
||||
* @param array $definitions
|
||||
* The new menu link definitions.
|
||||
*
|
||||
*/
|
||||
public function rebuild(array $definitions);
|
||||
|
||||
/**
|
||||
* Loads a menu link plugin definition from the storage.
|
||||
*
|
||||
* @param string $id
|
||||
* The menu link plugin ID.
|
||||
*
|
||||
* @return array|FALSE
|
||||
* The plugin definition, or FALSE if no definition was found for the ID.
|
||||
*/
|
||||
public function load($id);
|
||||
|
||||
/**
|
||||
* Loads multiple plugin definitions from the storage.
|
||||
*
|
||||
* @param array $ids
|
||||
* An array of plugin IDs.
|
||||
*
|
||||
* @return array
|
||||
* An array of plugin definition arrays keyed by plugin ID, which are the
|
||||
* actual definitions after the loadMultiple() including all those plugins
|
||||
* from $ids.
|
||||
*/
|
||||
public function loadMultiple(array $ids);
|
||||
|
||||
/**
|
||||
* Loads multiple plugin definitions from the storage based on properties.
|
||||
*
|
||||
* @param array $properties
|
||||
* The properties to filter by.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if an invalid property name is specified in $properties.
|
||||
*
|
||||
* @return array
|
||||
* An array of menu link definition arrays.
|
||||
*/
|
||||
public function loadByProperties(array $properties);
|
||||
|
||||
/**
|
||||
* Loads multiple plugin definitions from the storage 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 array
|
||||
* An array of menu link definitions keyed by ID and ordered by depth.
|
||||
*/
|
||||
public function loadByRoute($route_name, array $route_parameters = array(), $menu_name = NULL);
|
||||
|
||||
/**
|
||||
* Saves a plugin definition to the storage.
|
||||
*
|
||||
* @param array $definition
|
||||
* A definition for a \Drupal\Core\Menu\MenuLinkInterface plugin.
|
||||
*
|
||||
* @return array
|
||||
* The menu names affected by the save operation. This will be one menu
|
||||
* name if the link is saved to the sane menu, or two if it is saved to a
|
||||
* new menu.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Thrown if the storage back-end does not exist and could not be created.
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
* Thrown if the definition is invalid - for example, if the specified
|
||||
* parent would cause the links children to be moved to greater than the
|
||||
* maximum depth.
|
||||
*/
|
||||
public function save(array $definition);
|
||||
|
||||
/**
|
||||
* Deletes a menu link definition from the storage.
|
||||
*
|
||||
* @param string $id
|
||||
* The menu link plugin ID.
|
||||
*/
|
||||
public function delete($id);
|
||||
|
||||
/**
|
||||
* Loads a menu link tree from the storage.
|
||||
*
|
||||
* This function may be used build the data for a menu tree only, for example
|
||||
* to further massage the data manually before further processing happens.
|
||||
* MenuLinkTree::checkAccess() needs to be invoked afterwards.
|
||||
*
|
||||
* The tree order is maintained using an optimized algorithm, for example by
|
||||
* storing each parent in an individual field, see
|
||||
* https://www.drupal.org/node/141866 for more details. However, any details
|
||||
* of the storage should not be relied upon since it may be swapped with a
|
||||
* different implementation.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The name of the menu.
|
||||
* @param \Drupal\Core\Menu\MenuTreeParameters $parameters
|
||||
* The parameters to determine which menu links to be loaded into a tree.
|
||||
*
|
||||
* @return array
|
||||
* An array with 2 elements:
|
||||
* - tree: A fully built menu tree containing an array.
|
||||
* @see static::treeDataRecursive()
|
||||
* - route_names: An array of all route names used in the tree.
|
||||
*/
|
||||
public function loadTreeData($menu_name, MenuTreeParameters $parameters);
|
||||
|
||||
/**
|
||||
* Loads all the enabled menu links that are below the given ID.
|
||||
*
|
||||
* The returned links are not ordered, and visible children will be included
|
||||
* even if they have parent that is not enabled or ancestor so would not
|
||||
* normally appear in a rendered tree.
|
||||
*
|
||||
* @param string $id
|
||||
* The parent menu link ID.
|
||||
* @param int $max_relative_depth
|
||||
* The maximum relative depth of the children relative to the passed parent.
|
||||
*
|
||||
* @return array
|
||||
* An array of enabled link definitions, keyed by ID.
|
||||
*/
|
||||
public function loadAllChildren($id, $max_relative_depth = NULL);
|
||||
|
||||
/**
|
||||
* Loads all the IDs for menu links that are below the given ID.
|
||||
*
|
||||
* @param string $id
|
||||
* The parent menu link ID.
|
||||
*
|
||||
* @return array
|
||||
* An unordered array of plugin IDs corresponding to all children.
|
||||
*/
|
||||
public function getAllChildIds($id);
|
||||
|
||||
/**
|
||||
* Loads a subtree rooted by the given ID.
|
||||
*
|
||||
* The returned links are structured like those from loadTreeData().
|
||||
*
|
||||
* @param string $id
|
||||
* The menu link plugin ID.
|
||||
* @param int $max_relative_depth
|
||||
* (optional) The maximum depth of child menu links relative to the passed
|
||||
* in. Defaults to NULL, in which case the full subtree will be returned.
|
||||
*
|
||||
* @return array
|
||||
* An array with 2 elements:
|
||||
* - subtree: A fully built menu tree element or FALSE.
|
||||
* - route_names: An array of all route names used in the subtree.
|
||||
*/
|
||||
public function loadSubtreeData($id, $max_relative_depth = NULL);
|
||||
|
||||
/**
|
||||
* Returns all the IDs that represent the path to the root of the tree.
|
||||
*
|
||||
* @param string $id
|
||||
* A menu link ID.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of IDs with keys equal to values that represents the
|
||||
* path from the given ID to the root of the tree. If $id is an ID that
|
||||
* exists, the returned array will at least include it. An empty array is
|
||||
* returned if the ID does not exist in the storage. An example $id (8) with
|
||||
* two parents (1, 6) looks like the following:
|
||||
* @code
|
||||
* array(
|
||||
* 'p1' => 1,
|
||||
* 'p2' => 6,
|
||||
* 'p3' => 8,
|
||||
* 'p4' => 0,
|
||||
* 'p5' => 0,
|
||||
* 'p6' => 0,
|
||||
* 'p7' => 0,
|
||||
* 'p8' => 0,
|
||||
* 'p9' => 0
|
||||
* )
|
||||
* @endcode
|
||||
*/
|
||||
public function getRootPathIds($id);
|
||||
|
||||
/**
|
||||
* Finds expanded links in a menu given a set of possible parents.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu name.
|
||||
* @param array $parents
|
||||
* One or more parent IDs to match.
|
||||
*
|
||||
* @return array
|
||||
* The menu link IDs that are flagged as expanded in this menu.
|
||||
*/
|
||||
public function getExpanded($menu_name, array $parents);
|
||||
|
||||
/**
|
||||
* Finds the height of a subtree rooted by the given ID.
|
||||
*
|
||||
* @param string $id
|
||||
* The ID of an item in the storage.
|
||||
*
|
||||
* @return int
|
||||
* Returns the height of the subtree. This will be at least 1 if the ID
|
||||
* exists, or 0 if the ID does not exist in the storage.
|
||||
*/
|
||||
public function getSubtreeHeight($id);
|
||||
|
||||
/**
|
||||
* Determines whether a specific menu name is used in the tree.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu name.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the given menu name is used, otherwise FALSE.
|
||||
*/
|
||||
public function menuNameInUse($menu_name);
|
||||
|
||||
/**
|
||||
* Returns the used menu names in the tree storage.
|
||||
*
|
||||
* @return array
|
||||
* The menu names.
|
||||
*/
|
||||
public function getMenuNames();
|
||||
|
||||
/**
|
||||
* Counts the total number of menu links in one menu or all menus.
|
||||
*
|
||||
* @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);
|
||||
|
||||
}
|
184
core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php
Normal file
184
core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php
Normal file
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\StaticMenuLinkOverrides.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
|
||||
/**
|
||||
* Defines an implementation of the menu link override using a config file.
|
||||
*/
|
||||
class StaticMenuLinkOverrides implements StaticMenuLinkOverridesInterface {
|
||||
|
||||
/**
|
||||
* The config name used to store the overrides.
|
||||
*
|
||||
* This configuration can not be overridden by configuration overrides because
|
||||
* menu links and these overrides are cached in a way that is not override
|
||||
* aware.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $configName = 'core.menu.static_menu_link_overrides';
|
||||
|
||||
/**
|
||||
* The menu link overrides config object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* The config factory object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* Constructs a StaticMenuLinkOverrides object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* A configuration factory instance.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory) {
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration object when needed.
|
||||
*
|
||||
* Since this service is injected into all static menu link objects, but
|
||||
* only used when updating one, avoid actually loading the config when it's
|
||||
* not needed.
|
||||
*/
|
||||
protected function getConfig() {
|
||||
if (empty($this->config)) {
|
||||
// Get an override free and editable configuration object.
|
||||
$this->config = $this->configFactory->getEditable($this->configName);
|
||||
}
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reload() {
|
||||
$this->config = NULL;
|
||||
$this->configFactory->reset($this->configName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadOverride($id) {
|
||||
$all_overrides = $this->getConfig()->get('definitions');
|
||||
$id = static::encodeId($id);
|
||||
return $id && isset($all_overrides[$id]) ? $all_overrides[$id] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteMultipleOverrides(array $ids) {
|
||||
$all_overrides = $this->getConfig()->get('definitions');
|
||||
$save = FALSE;
|
||||
foreach ($ids as $id) {
|
||||
$id = static::encodeId($id);
|
||||
if (isset($all_overrides[$id])) {
|
||||
unset($all_overrides[$id]);
|
||||
$save = TRUE;
|
||||
}
|
||||
}
|
||||
if ($save) {
|
||||
$this->getConfig()->set('definitions', $all_overrides)->save();
|
||||
}
|
||||
return $save;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteOverride($id) {
|
||||
return $this->deleteMultipleOverrides(array($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMultipleOverrides(array $ids) {
|
||||
$result = array();
|
||||
if ($ids) {
|
||||
$all_overrides = $this->getConfig()->get('definitions') ?: array();
|
||||
foreach ($ids as $id) {
|
||||
$encoded_id = static::encodeId($id);
|
||||
if (isset($all_overrides[$encoded_id])) {
|
||||
$result[$id] = $all_overrides[$encoded_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveOverride($id, array $definition) {
|
||||
// Only allow to override a specific subset of the keys.
|
||||
$expected = array(
|
||||
'menu_name' => '',
|
||||
'parent' => '',
|
||||
'weight' => 0,
|
||||
'expanded' => FALSE,
|
||||
'enabled' => FALSE,
|
||||
);
|
||||
// Filter the overrides to only those that are expected.
|
||||
$definition = array_intersect_key($definition, $expected);
|
||||
// Ensure all values are set.
|
||||
$definition = $definition + $expected;
|
||||
if ($definition) {
|
||||
// Cast keys to avoid config schema during save.
|
||||
$definition['menu_name'] = (string) $definition['menu_name'];
|
||||
$definition['parent'] = (string) $definition['parent'];
|
||||
$definition['weight'] = (int) $definition['weight'];
|
||||
$definition['expanded'] = (bool) $definition['expanded'];
|
||||
$definition['enabled'] = (bool) $definition['enabled'];
|
||||
|
||||
$id = static::encodeId($id);
|
||||
$all_overrides = $this->getConfig()->get('definitions');
|
||||
// Combine with any existing data.
|
||||
$all_overrides[$id] = $definition + $this->loadOverride($id);
|
||||
$this->getConfig()->set('definitions', $all_overrides)->save(TRUE);
|
||||
}
|
||||
return array_keys($definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->getConfig()->getCacheTags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the ID by replacing dots with double underscores.
|
||||
*
|
||||
* This is done because config schema uses dots for its internal type
|
||||
* hierarchy. Double underscores are converted to triple underscores to
|
||||
* avoid accidental conflicts.
|
||||
*
|
||||
* @param string $id
|
||||
* The menu plugin ID.
|
||||
*
|
||||
* @return string
|
||||
* The menu plugin ID with double underscore instead of dots.
|
||||
*/
|
||||
protected static function encodeId($id) {
|
||||
return strtr($id, array('.' => '__', '__' => '___'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\StaticMenuLinkOverridesInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Menu;
|
||||
|
||||
/**
|
||||
* Defines an interface for objects which overrides menu links defined in YAML.
|
||||
*/
|
||||
interface StaticMenuLinkOverridesInterface {
|
||||
|
||||
/**
|
||||
* Reloads the overrides from config.
|
||||
*
|
||||
* Forces all overrides to be reloaded from config storage to compare the
|
||||
* override value with the value submitted during test form submission.
|
||||
*/
|
||||
public function reload();
|
||||
|
||||
/**
|
||||
* Loads any overrides to the definition of a static (YAML-defined) link.
|
||||
*
|
||||
* @param string $id
|
||||
* A menu link plugin ID.
|
||||
*
|
||||
* @return array|NULL
|
||||
* An override with following supported keys:
|
||||
* - parent
|
||||
* - weight
|
||||
* - menu_name
|
||||
* - expanded
|
||||
* - enabled
|
||||
* or NULL if there is no override for the given ID.
|
||||
*/
|
||||
public function loadOverride($id);
|
||||
|
||||
/**
|
||||
* Deletes any overrides to the definition of a static (YAML-defined) link.
|
||||
*
|
||||
* @param string $id
|
||||
* A menu link plugin ID.
|
||||
*/
|
||||
public function deleteOverride($id);
|
||||
|
||||
/**
|
||||
* Deletes multiple overrides to definitions of static (YAML-defined) links.
|
||||
*
|
||||
* @param array $ids
|
||||
* Array of menu link plugin IDs.
|
||||
*/
|
||||
public function deleteMultipleOverrides(array $ids);
|
||||
|
||||
/**
|
||||
* Loads overrides to multiple definitions of a static (YAML-defined) link.
|
||||
*
|
||||
* @param array $ids
|
||||
* Array of menu link plugin IDs.
|
||||
*
|
||||
* @return array
|
||||
* One or override keys by plugin ID.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
|
||||
*/
|
||||
public function loadMultipleOverrides(array $ids);
|
||||
|
||||
/**
|
||||
* Saves the override.
|
||||
*
|
||||
* @param string $id
|
||||
* A menu link plugin ID.
|
||||
* @param array $definition
|
||||
* The definition values to override. Supported keys:
|
||||
* - menu_name
|
||||
* - parent
|
||||
* - weight
|
||||
* - expanded
|
||||
* - enabled
|
||||
*
|
||||
* @return array
|
||||
* A list of properties which got saved.
|
||||
*/
|
||||
public function saveOverride($id, array $definition);
|
||||
|
||||
/**
|
||||
* The unique cache tag associated with this menu link override.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of cache tags.
|
||||
*/
|
||||
public function getCacheTags();
|
||||
|
||||
}
|
603
core/lib/Drupal/Core/Menu/menu.api.php
Normal file
603
core/lib/Drupal/Core/Menu/menu.api.php
Normal file
|
@ -0,0 +1,603 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks and documentation related to the menu system, routing, and links.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup menu Menu and routing system
|
||||
* @{
|
||||
* Define the navigation menus, and route page requests to code based on URLs.
|
||||
*
|
||||
* @section sec_overview Overview and terminology
|
||||
* The Drupal routing system defines how Drupal responds to URL requests that
|
||||
* the web server passes on to Drupal. The routing system is based on the
|
||||
* @link http://symfony.com Symfony framework. @endlink The central idea is
|
||||
* that Drupal subsystems and modules can register routes (basically, URL
|
||||
* paths and context); they can also register to respond dynamically to
|
||||
* routes, for more flexibility. When Drupal receives a URL request, it will
|
||||
* attempt to match the request to a registered route, and query dynamic
|
||||
* responders. If a match is made, Drupal will then instantiate the required
|
||||
* classes, gather the data, format it, and send it back to the web browser.
|
||||
* Otherwise, Drupal will return a 404 or 403 response.
|
||||
*
|
||||
* The menu system uses routes; it is used for navigation menus, local tasks,
|
||||
* local actions, and contextual links:
|
||||
* - Navigation menus are hierarchies of menu links; links point to routes or
|
||||
* URLs.
|
||||
* - Menu links and their hierarchies can be defined by Drupal subsystems
|
||||
* and modules, or created in the user interface using the Menu UI module.
|
||||
* - Local tasks are groups of related routes. Local tasks are usually rendered
|
||||
* as a group of tabs.
|
||||
* - Local actions are used for operations such as adding a new item on a page
|
||||
* that lists items of some type. Local actions are usually rendered as
|
||||
* buttons.
|
||||
* - Contextual links are actions that are related to sections of rendered
|
||||
* output, and are usually rendered as a pop-up list of links. The
|
||||
* Contextual Links module handles the gathering and rendering of contextual
|
||||
* links.
|
||||
*
|
||||
* The following sections of this topic provide an overview of the routing and
|
||||
* menu APIs. For more detailed information, see
|
||||
* https://www.drupal.org/developing/api/8/routing and
|
||||
* https://www.drupal.org/developing/api/8/menu
|
||||
*
|
||||
* @section sec_register Registering simple routes
|
||||
* To register a route, add lines similar to this to a module_name.routing.yml
|
||||
* file in your top-level module directory:
|
||||
* @code
|
||||
* dblog.overview:
|
||||
* path: '/admin/reports/dblog'
|
||||
* defaults:
|
||||
* _controller: '\Drupal\dblog\Controller\DbLogController::overview'
|
||||
* _title: 'Recent log messages'
|
||||
* requirements:
|
||||
* _permission: 'access site reports'
|
||||
* @endcode
|
||||
* Some notes:
|
||||
* - The first line is the machine name of the route. Typically, it is prefixed
|
||||
* by the machine name of the module that defines the route, or the name of
|
||||
* a subsystem.
|
||||
* - The 'path' line gives the URL path of the route (relative to the site's
|
||||
* base URL).
|
||||
* - The 'defaults' section tells how to build the main content of the route,
|
||||
* and can also give other information, such as the page title and additional
|
||||
* arguments for the route controller method. There are several possibilities
|
||||
* for how to build the main content, including:
|
||||
* - _controller: A callable, usually a method on a page controller class
|
||||
* (see @ref sec_controller below for details).
|
||||
* - _form: A form controller class. See the
|
||||
* @link form_api Form API topic @endlink for more information about
|
||||
* form controllers.
|
||||
* - _entity_form: A form for editing an entity. See the
|
||||
* @link entity_api Entity API topic @endlink for more information.
|
||||
* - The 'requirements' section is used in Drupal to give access permission
|
||||
* instructions (it has other uses in the Symfony framework). Most
|
||||
* routes have a simple permission-based access scheme, as shown in this
|
||||
* example. See the @link user_api Permission system topic @endlink for
|
||||
* more information about permissions.
|
||||
*
|
||||
* See https://www.drupal.org/node/2092643 for more details about *.routing.yml
|
||||
* files, and https://www.drupal.org/node/2122201 for information on how to
|
||||
* set up dynamic routes. The @link events Events topic @endlink is also
|
||||
* relevant to dynamic routes.
|
||||
*
|
||||
* @section sec_placeholders Defining routes with placeholders
|
||||
* Some routes have placeholders in them, and these can also be defined in a
|
||||
* module_name.routing.yml file, as in this example from the Block module:
|
||||
* @code
|
||||
* entity.block.edit_form:
|
||||
* path: '/admin/structure/block/manage/{block}'
|
||||
* defaults:
|
||||
* _entity_form: 'block.default'
|
||||
* _title: 'Configure block'
|
||||
* requirements:
|
||||
* _entity_access: 'block.update'
|
||||
* @endcode
|
||||
* In the path, '{block}' is a placeholder - it will be replaced by the
|
||||
* ID of the block that is being configured by the entity system. See the
|
||||
* @link entity_api Entity API topic @endlink for more information.
|
||||
*
|
||||
* @section sec_controller Route controllers for simple routes
|
||||
* For simple routes, after you have defined the route in a *.routing.yml file
|
||||
* (see @ref sec_register above), the next step is to define a page controller
|
||||
* class and method. Page controller classes do not necessarily need to
|
||||
* implement any particular interface or extend any particular base class. The
|
||||
* only requirement is that the method specified in your *.routing.yml file
|
||||
* returns:
|
||||
* - A render array (see the
|
||||
* @link theme_render Theme and render topic @endlink for more information).
|
||||
* This render array is then rendered in the requested format (HTML, dialog,
|
||||
* modal, AJAX are supported by default). In the case of HTML, it will be
|
||||
* surrounded by blocks by default: the Block module is enabled by default,
|
||||
* and hence its Page Display Variant that surrounds the main content with
|
||||
* blocks is also used by default.
|
||||
* - A \Symfony\Component\HttpFoundation\Response object.
|
||||
* As a note, if your module registers multiple simple routes, it is usual
|
||||
* (and usually easiest) to put all of their methods on one controller class.
|
||||
*
|
||||
* If the route has placeholders (see @ref sec_placeholders above) the
|
||||
* placeholders will be passed to the method (using reflection) by name.
|
||||
* For example, the placeholder '{myvar}' in a route will become the $myvar
|
||||
* parameter to the method.
|
||||
*
|
||||
* Most controllers will need to display some information stored in the Drupal
|
||||
* database, which will involve using one or more Drupal services (see the
|
||||
* @link container Services and container topic @endlink). In order to properly
|
||||
* inject services, a controller should implement
|
||||
* \Drupal\Core\DependencyInjection\ContainerInjectionInterface; simple
|
||||
* controllers can do this by extending the
|
||||
* \Drupal\Core\Controller\ControllerBase class. See
|
||||
* \Drupal\dblog\Controller\DbLogController for a straightforward example of
|
||||
* a controller class.
|
||||
*
|
||||
* @section sec_links Defining menu links for the administrative menu
|
||||
* Routes for administrative tasks can be added to the main Drupal
|
||||
* administrative menu hierarchy. To do this, add lines like the following to a
|
||||
* module_name.links.menu.yml file (in the top-level directory for your module):
|
||||
* @code
|
||||
* dblog.overview:
|
||||
* title: 'Recent log messages'
|
||||
* parent: system.admin_reports
|
||||
* description: 'View events that have recently been logged.'
|
||||
* route_name: dblog.overview
|
||||
* weight: -1
|
||||
* @endcode
|
||||
* Some notes:
|
||||
* - The first line is the machine name for your menu link, which usually
|
||||
* matches the machine name of the route (given in the 'route_name' line).
|
||||
* - parent: The machine name of the menu link that is the parent in the
|
||||
* administrative hierarchy. See system.links.menu.yml to find the main
|
||||
* skeleton of the hierarchy.
|
||||
* - weight: Lower (negative) numbers come before higher (positive) numbers,
|
||||
* for menu items with the same parent.
|
||||
*
|
||||
* Discovered menu links from other modules can be altered using
|
||||
* hook_menu_links_discovered_alter().
|
||||
*
|
||||
* @todo Derivatives will probably be defined for these; when they are, add
|
||||
* documentation here.
|
||||
*
|
||||
* @section sec_tasks Defining groups of local tasks (tabs)
|
||||
* Local tasks appear as tabs on a page when there are at least two defined for
|
||||
* a route, including the base route as the main tab, and additional routes as
|
||||
* other tabs. Static local tasks can be defined by adding lines like the
|
||||
* following to a module_name.links.task.yml file (in the top-level directory
|
||||
* for your module):
|
||||
* @code
|
||||
* book.admin:
|
||||
* route_name: book.admin
|
||||
* title: 'List'
|
||||
* base_route: book.admin
|
||||
* book.settings:
|
||||
* route_name: book.settings
|
||||
* title: 'Settings'
|
||||
* base_route: book.admin
|
||||
* weight: 100
|
||||
* @endcode
|
||||
* Some notes:
|
||||
* - The first line is the machine name for your local task, which usually
|
||||
* matches the machine name of the route (given in the 'route_name' line).
|
||||
* - base_route: The machine name of the main task (tab) for the set of local
|
||||
* tasks.
|
||||
* - weight: Lower (negative) numbers come before higher (positive) numbers,
|
||||
* for tasks on the same base route. If there is a tab whose route
|
||||
* matches the base route, that will be the default/first tab shown.
|
||||
*
|
||||
* Local tasks from other modules can be altered using
|
||||
* hook_menu_local_tasks_alter().
|
||||
*
|
||||
* @todo Derivatives are in flux for these; when they are more stable, add
|
||||
* documentation here.
|
||||
*
|
||||
* @section sec_actions Defining local actions for routes
|
||||
* Local actions can be defined for operations related to a given route. For
|
||||
* instance, adding content is a common operation for the content management
|
||||
* page, so it should be a local action. Static local actions can be
|
||||
* defined by adding lines like the following to a
|
||||
* module_name.links.action.yml file (in the top-level directory for your
|
||||
* module):
|
||||
* @code
|
||||
* node.add_page:
|
||||
* route_name: node.add_page
|
||||
* title: 'Add content'
|
||||
* appears_on:
|
||||
* - system.admin_content
|
||||
* @endcode
|
||||
* Some notes:
|
||||
* - The first line is the machine name for your local action, which usually
|
||||
* matches the machine name of the route (given in the 'route_name' line).
|
||||
* - appears_on: Machine names of one or more routes that this local task
|
||||
* should appear on.
|
||||
*
|
||||
* Local actions from other modules can be altered using
|
||||
* hook_menu_local_actions_alter().
|
||||
*
|
||||
* @todo Derivatives are in flux for these; when they are more stable, add
|
||||
* documentation here.
|
||||
*
|
||||
* @section sec_contextual Defining contextual links
|
||||
* Contextual links are displayed by the Contextual Links module for user
|
||||
* interface elements whose render arrays have a '#contextual_links' element
|
||||
* defined. For example, a block render array might look like this, in part:
|
||||
* @code
|
||||
* array(
|
||||
* '#contextual_links' => array(
|
||||
* 'block' => array(
|
||||
* 'route_parameters' => array('block' => $entity->id()),
|
||||
* ),
|
||||
* ),
|
||||
* @endcode
|
||||
* In this array, the outer key 'block' defines a "group" for contextual
|
||||
* links, and the inner array provides values for the route's placeholder
|
||||
* parameters (see @ref sec_placeholders above).
|
||||
*
|
||||
* To declare that a defined route should be a contextual link for a
|
||||
* contextual links group, put lines like the following in a
|
||||
* module_name.links.contextual.yml file (in the top-level directory for your
|
||||
* module):
|
||||
* @code
|
||||
* block_configure:
|
||||
* title: 'Configure block'
|
||||
* route_name: 'entity.block.edit_form'
|
||||
* group: 'block'
|
||||
* @endcode
|
||||
* Some notes:
|
||||
* - The first line is the machine name for your contextual link, which usually
|
||||
* matches the machine name of the route (given in the 'route_name' line).
|
||||
* - group: This needs to match the link group defined in the render array.
|
||||
*
|
||||
* Contextual links from other modules can be altered using
|
||||
* hook_contextual_links_alter().
|
||||
*
|
||||
* @todo Derivatives are in flux for these; when they are more stable, add
|
||||
* documentation here.
|
||||
*
|
||||
* @section sec_rendering Rendering menus
|
||||
* Once you have created menus (that contain menu links), you want to render
|
||||
* them. Drupal provides a block (Drupal\system\Plugin\Block\SystemMenuBlock) to
|
||||
* do so.
|
||||
*
|
||||
* However, perhaps you have more advanced needs and you're not satisfied with
|
||||
* what the menu blocks offer you. If that's the case, you'll want to:
|
||||
* - Instantiate \Drupal\Core\Menu\MenuTreeParameters, and set its values to
|
||||
* match your needs. Alternatively, you can use
|
||||
* MenuLinkTree::getCurrentRouteMenuTreeParameters() to get a typical
|
||||
* default set of parameters, and then customize them to suit your needs.
|
||||
* - Call \Drupal\Core\MenuLinkTree::load() with your menu link tree parameters,
|
||||
* this will return a menu link tree.
|
||||
* - Pass the menu tree to \Drupal\Core\Menu\MenuLinkTree::transform() to apply
|
||||
* menu link tree manipulators that transform the tree. You will almost always
|
||||
* want to apply access checking. The manipulators that you will typically
|
||||
* need can be found in \Drupal\Core\Menu\DefaultMenuTreeManipulators.
|
||||
* - Potentially write a custom menu tree manipulator, see
|
||||
* \Drupal\Core\Menu\DefaultMenuTreeManipulators for examples. This is only
|
||||
* necessary if you want to do things like adding extra metadata to rendered
|
||||
* links to display icons next to them.
|
||||
* - Pass the menu tree to \Drupal\Core\Menu\MenuLinkTree::build(), this will
|
||||
* build a renderable array.
|
||||
*
|
||||
* Combined, that would look like this:
|
||||
* @code
|
||||
* $menu_tree = \Drupal::menuTree();
|
||||
* $menu_name = 'my_menu';
|
||||
*
|
||||
* // Build the typical default set of menu tree parameters.
|
||||
* $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
|
||||
*
|
||||
* // Load the tree based on this set of parameters.
|
||||
* $tree = $menu_tree->load($menu_name, $parameters);
|
||||
*
|
||||
* // Transform the tree using the manipulators you want.
|
||||
* $manipulators = array(
|
||||
* // Only show links that are accessible for the current user.
|
||||
* array('callable' => 'menu.default_tree_manipulators:checkAccess'),
|
||||
* // Use the default sorting of menu links.
|
||||
* array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
|
||||
* );
|
||||
* $tree = $menu_tree->transform($tree, $manipulators);
|
||||
*
|
||||
* // Finally, build a renderable array from the transformed tree.
|
||||
* $menu = $menu_tree->build($tree);
|
||||
*
|
||||
* $menu_html = drupal_render($menu);
|
||||
* @endcode
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alters all the menu links discovered by the menu link plugin manager.
|
||||
*
|
||||
* @param array $links
|
||||
* The link definitions to be altered.
|
||||
*
|
||||
* @return array
|
||||
* An array of discovered menu links. Each link has a key that is the machine
|
||||
* name, which must be unique. By default, use the route name as the
|
||||
* machine name. In cases where multiple links use the same route name, such
|
||||
* as two links to the same page in different menus, or two links using the
|
||||
* same route name but different route parameters, the suggested machine name
|
||||
* patten is the route name followed by a dot and a unique suffix. For
|
||||
* example, an additional logout link might have a machine name of
|
||||
* user.logout.navigation, and default links provided to edit the article and
|
||||
* page content types could use machine names
|
||||
* entity.node_type.edit_form.article and entity.node_type.edit_form.page.
|
||||
* Since the machine name may be arbitrary, you should never write code that
|
||||
* assumes it is identical to the route name.
|
||||
*
|
||||
* The value corresponding to each machine name key is an associative array
|
||||
* that may contain the following key-value pairs:
|
||||
* - title: (required) The untranslated title of the menu link.
|
||||
* - description: The untranslated description of the link.
|
||||
* - route_name: (optional) The route name to be used to build the path.
|
||||
* Either the route_name or url element must be provided.
|
||||
* - route_parameters: (optional) The route parameters to build the path.
|
||||
* - url: (optional) If you have an external link use this element instead of
|
||||
* providing route_name.
|
||||
* - parent: (optional) The machine name of the link that is this link's menu
|
||||
* parent.
|
||||
* - weight: (optional) An integer that determines the relative position of
|
||||
* items in the menu; higher-weighted items sink. Defaults to 0. Menu items
|
||||
* with the same weight are ordered alphabetically.
|
||||
* - menu_name: (optional) The machine name of a menu to put the link in, if
|
||||
* not the default Tools menu.
|
||||
* - expanded: (optional) If set to TRUE, and if a menu link is provided for
|
||||
* this menu item (as a result of other properties), then the menu link is
|
||||
* always expanded, equivalent to its 'always expanded' checkbox being set
|
||||
* in the UI.
|
||||
* - options: (optional) An array of options to be passed to _l() when
|
||||
* generating a link from this menu item.
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_menu_links_discovered_alter(&$links) {
|
||||
// Change the weight and title of the user.logout link.
|
||||
$links['user.logout']['weight'] = -10;
|
||||
$links['user.logout']['title'] = 'Logout';
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter tabs and actions displayed on the page before they are rendered.
|
||||
*
|
||||
* This hook is invoked by menu_local_tasks(). The system-determined tabs and
|
||||
* actions are passed in by reference. Additional tabs or actions may be added.
|
||||
*
|
||||
* Each tab or action is an associative array containing:
|
||||
* - #theme: The theme function to use to render.
|
||||
* - #link: An associative array containing:
|
||||
* - title: The localized title of the link.
|
||||
* - href: The system path to link to.
|
||||
* - localized_options: An array of options to pass to _l().
|
||||
* - #weight: The link's weight compared to other links.
|
||||
* - #active: Whether the link should be marked as 'active'.
|
||||
*
|
||||
* @param array $data
|
||||
* An associative array containing:
|
||||
* - actions: A list of of actions keyed by their href, each one being an
|
||||
* associative array as described above.
|
||||
* - tabs: A list of (up to 2) tab levels that contain a list of of tabs keyed
|
||||
* by their href, each one being an associative array as described above.
|
||||
* @param string $route_name
|
||||
* The route name of the page.
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_menu_local_tasks(&$data, $route_name) {
|
||||
// Add an action linking to node/add to all pages.
|
||||
$data['actions']['node/add'] = array(
|
||||
'#theme' => 'menu_local_action',
|
||||
'#link' => array(
|
||||
'title' => t('Add content'),
|
||||
'url' => Url::fromRoute('node.add_page'),
|
||||
'localized_options' => array(
|
||||
'attributes' => array(
|
||||
'title' => t('Add content'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Add a tab linking to node/add to all pages.
|
||||
$data['tabs'][0]['node/add'] = array(
|
||||
'#theme' => 'menu_local_task',
|
||||
'#link' => array(
|
||||
'title' => t('Example tab'),
|
||||
'url' => Url::fromRoute('node.add_page'),
|
||||
'localized_options' => array(
|
||||
'attributes' => array(
|
||||
'title' => t('Add content'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter tabs and actions displayed on the page before they are rendered.
|
||||
*
|
||||
* This hook is invoked by menu_local_tasks(). The system-determined tabs and
|
||||
* actions are passed in by reference. Existing tabs or actions may be altered.
|
||||
*
|
||||
* @param array $data
|
||||
* An associative array containing tabs and actions. See
|
||||
* hook_menu_local_tasks() for details.
|
||||
* @param string $route_name
|
||||
* The route name of the page.
|
||||
*
|
||||
* @see hook_menu_local_tasks()
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_menu_local_tasks_alter(&$data, $route_name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter local actions plugins.
|
||||
*
|
||||
* @param array $local_actions
|
||||
* The array of local action plugin definitions, keyed by plugin ID.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\LocalActionInterface
|
||||
* @see \Drupal\Core\Menu\LocalActionManager
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_menu_local_actions_alter(&$local_actions) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter local tasks plugins.
|
||||
*
|
||||
* @param array $local_tasks
|
||||
* The array of local tasks plugin definitions, keyed by plugin ID.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\LocalTaskInterface
|
||||
* @see \Drupal\Core\Menu\LocalTaskManager
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_local_tasks_alter(&$local_tasks) {
|
||||
// Remove a specified local task plugin.
|
||||
unset($local_tasks['example_plugin_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter contextual links before they are rendered.
|
||||
*
|
||||
* This hook is invoked by
|
||||
* \Drupal\Core\Menu\ContextualLinkManager::getContextualLinkPluginsByGroup().
|
||||
* The system-determined contextual links are passed in by reference. Additional
|
||||
* links may be added and existing links can be altered.
|
||||
*
|
||||
* Each contextual link contains the following entries:
|
||||
* - title: The localized title of the link.
|
||||
* - route_name: The route name of the link.
|
||||
* - route_parameters: The route parameters of the link.
|
||||
* - localized_options: An array of URL options.
|
||||
* - (optional) weight: The weight of the link, which is used to sort the links.
|
||||
*
|
||||
*
|
||||
* @param array $links
|
||||
* An associative array containing contextual links for the given $group,
|
||||
* as described above. The array keys are used to build CSS class names for
|
||||
* contextual links and must therefore be unique for each set of contextual
|
||||
* links.
|
||||
* @param string $group
|
||||
* The group of contextual links being rendered.
|
||||
* @param array $route_parameters.
|
||||
* The route parameters passed to each route_name of the contextual links.
|
||||
* For example:
|
||||
* @code
|
||||
* array('node' => $node->id())
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Menu\ContextualLinkManager
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_contextual_links_alter(array &$links, $group, array $route_parameters) {
|
||||
if ($group == 'menu') {
|
||||
// Dynamically use the menu name for the title of the menu_edit contextual
|
||||
// link.
|
||||
$menu = \Drupal::entityManager()->getStorage('menu')->load($route_parameters['menu']);
|
||||
$links['menu_edit']['title'] = t('Edit menu: !label', array('!label' => $menu->label()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the plugin definition of contextual links.
|
||||
*
|
||||
* @param array $contextual_links
|
||||
* An array of contextual_links plugin definitions, keyed by contextual link
|
||||
* ID. Each entry contains the following keys:
|
||||
* - title: The displayed title of the link
|
||||
* - route_name: The route_name of the contextual link to be displayed
|
||||
* - group: The group under which the contextual links should be added to.
|
||||
* Possible values are e.g. 'node' or 'menu'.
|
||||
*
|
||||
* @see \Drupal\Core\Menu\ContextualLinkManager
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_contextual_links_plugins_alter(array &$contextual_links) {
|
||||
$contextual_links['menu_edit']['title'] = 'Edit the menu';
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform alterations to the breadcrumb built by the BreadcrumbManager.
|
||||
*
|
||||
* @param array $breadcrumb
|
||||
* An array of breadcrumb link a tags, returned by the breadcrumb manager
|
||||
* build method, for example
|
||||
* @code
|
||||
* array('<a href="/">Home</a>');
|
||||
* @endcode
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
* @param array $context
|
||||
* May include the following key:
|
||||
* - builder: the instance of
|
||||
* \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface that constructed this
|
||||
* breadcrumb, or NULL if no builder acted based on the current attributes.
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
function hook_system_breadcrumb_alter(array &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) {
|
||||
// Add an item to the end of the breadcrumb.
|
||||
$breadcrumb[] = Drupal::l(t('Text'), 'example_route_name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the parameters for links.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array of variables defining a link. The link may be either a
|
||||
* "route link" using \Drupal\Core\Utility\LinkGenerator::link(), which is
|
||||
* exposed as the 'link_generator' service or a link generated by _l(). If the
|
||||
* link is a "route link", 'route_name' will be set, otherwise 'path' will be
|
||||
* set. The following keys can be altered:
|
||||
* - text: The link text for the anchor tag as a translated string.
|
||||
* - url_is_active: Whether or not the link points to the currently active
|
||||
* URL.
|
||||
* - url: The \Drupal\Core\Url object.
|
||||
* - options: An associative array of additional options that will be passed
|
||||
* to either \Drupal\Core\Routing\UrlGenerator::generateFromPath() or
|
||||
* \Drupal\Core\Routing\UrlGenerator::generateFromRoute() to generate the
|
||||
* href attribute for this link, and also used when generating the link.
|
||||
* Defaults to an empty array. It may contain the following elements:
|
||||
* - 'query': An array of query key/value-pairs (without any URL-encoding) to
|
||||
* append to the URL.
|
||||
* - absolute: Whether to force the output to be an absolute link (beginning
|
||||
* with http:). Useful for links that will be displayed outside the site,
|
||||
* such as in an RSS feed. Defaults to FALSE.
|
||||
* - language: An optional language object. May affect the rendering of
|
||||
* the anchor tag, such as by adding a language prefix to the path.
|
||||
* - attributes: An associative array of HTML attributes to apply to the
|
||||
* anchor tag. If element 'class' is included, it must be an array; 'title'
|
||||
* must be a string; other elements are more flexible, as they just need
|
||||
* to work as an argument for the constructor of the class
|
||||
* Drupal\Core\Template\Attribute($options['attributes']).
|
||||
* - html: Whether or not HTML should be allowed as the link text. If FALSE,
|
||||
* the text will be run through
|
||||
* \Drupal\Component\Utility\SafeMarkup::checkPlain() before being output.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\UrlGenerator::generateFromPath()
|
||||
* @see \Drupal\Core\Routing\UrlGenerator::generateFromRoute()
|
||||
*/
|
||||
function hook_link_alter(&$variables) {
|
||||
// Add a warning to the end of route links to the admin section.
|
||||
if (isset($variables['route_name']) && strpos($variables['route_name'], 'admin') !== FALSE) {
|
||||
$variables['text'] .= ' (Warning!)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
Reference in a new issue