Commit fd2db9cd authored by catch's avatar catch

Issue #2301317 by pwolanin, effulgentsia, Wim Leers, dawehner, alexpott:...

Issue #2301317 by pwolanin, effulgentsia, Wim Leers, dawehner, alexpott: MenuLinkNG part4: Conversion.
parent 16caf92d
...@@ -277,7 +277,7 @@ services: ...@@ -277,7 +277,7 @@ services:
arguments: ['@menu.tree_storage', '@menu_link.static.overrides', '@module_handler'] arguments: ['@menu.tree_storage', '@menu_link.static.overrides', '@module_handler']
menu.link_tree: menu.link_tree:
class: Drupal\Core\Menu\MenuLinkTree class: Drupal\Core\Menu\MenuLinkTree
arguments: ['@menu.tree_storage', '@plugin.manager.menu.link', '@router.route_provider', '@menu.active_trail', '@controller_resolver'] arguments: ['@menu.tree_storage', '@plugin.manager.menu.link', '@router.route_provider', '@menu.active_trail', '@controller_resolver', '@cache.menu', '@current_route_match']
menu.default_tree_manipulators: menu.default_tree_manipulators:
class: Drupal\Core\Menu\DefaultMenuLinkTreeManipulators class: Drupal\Core\Menu\DefaultMenuLinkTreeManipulators
arguments: ['@access_manager', '@current_user'] arguments: ['@access_manager', '@current_user']
...@@ -443,7 +443,12 @@ services: ...@@ -443,7 +443,12 @@ services:
arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state'] arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state']
router.rebuild_subscriber: router.rebuild_subscriber:
class: Drupal\Core\EventSubscriber\RouterRebuildSubscriber class: Drupal\Core\EventSubscriber\RouterRebuildSubscriber
arguments: ['@router.builder', '@lock'] arguments: ['@router.builder']
tags:
- { name: event_subscriber }
menu.rebuild_subscriber:
class: Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber
arguments: ['@lock', '@plugin.manager.menu.link']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
path.alias_storage: path.alias_storage:
......
...@@ -153,8 +153,8 @@ ...@@ -153,8 +153,8 @@
* - weight: Lower (negative) numbers come before higher (positive) numbers, * - weight: Lower (negative) numbers come before higher (positive) numbers,
* for menu items with the same parent. * for menu items with the same parent.
* *
* Menu items from other modules can be altered using * Discovered menu links from other modules can be altered using
* hook_menu_link_defaults_alter(). * hook_menu_links_discovered_alter().
* *
* @todo Derivatives will probably be defined for these; when they are, add * @todo Derivatives will probably be defined for these; when they are, add
* documentation here. * documentation here.
...@@ -264,18 +264,56 @@ ...@@ -264,18 +264,56 @@
const MENU_MAX_DEPTH = 9; const MENU_MAX_DEPTH = 9;
/** /**
* Reserved key to identify the most specific menu link for a given path. * @section 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.
* *
* The value of this constant is a hash of the constant name. We use the hash * However, perhaps you have more advanced needs and you're not satisfied with
* so that the reserved key is over 32 characters in length and will not * what the menu blocks offer you. If that's the case, you'll want to:
* collide with allowed menu names: * - 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 * @code
* sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91 * $menu_tree = \Drupal::menuTree();
* @endcode * $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);
* *
* @see menu_link_get_preferred() * $menu_html = drupal_render($menu);
* @endcode
*/ */
const MENU_PREFERRED_LINK = '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91';
/** /**
* Localizes a menu link title using t() if possible. * Localizes a menu link title using t() if possible.
...@@ -367,21 +405,40 @@ function _menu_link_translate(&$item) { ...@@ -367,21 +405,40 @@ function _menu_link_translate(&$item) {
* Implements template_preprocess_HOOK() for theme_menu_tree(). * Implements template_preprocess_HOOK() for theme_menu_tree().
*/ */
function template_preprocess_menu_tree(&$variables) { function template_preprocess_menu_tree(&$variables) {
$variables['tree'] = $variables['tree']['#children']; if (isset($variables['tree']['#heading'])) {
} $variables['heading'] = $variables['tree']['#heading'];
$heading = &$variables['heading'];
// Convert a string heading into an array, using a H2 tag by default.
if (is_string($heading)) {
$heading = array('text' => $heading);
}
// Merge in default array properties into $heading.
$heading += array(
'level' => 'h2',
'attributes' => array(),
);
// @todo Remove backwards compatibility for $heading['class'].
// https://www.drupal.org/node/2310341
if (isset($heading['class'])) {
$heading['attributes']['class'] = $heading['class'];
}
// Convert the attributes array into an Attribute object.
$heading['attributes'] = new Attribute($heading['attributes']);
$heading['text'] = String::checkPlain($heading['text']);
}
/** if (isset($variables['tree']['#attributes'])) {
* Returns HTML for a wrapper for a menu sub-tree. $variables['attributes'] = new Attribute($variables['tree']['#attributes']);
* }
* @param $variables else {
* An associative array containing: $variables['attributes'] = new Attribute();
* - tree: An HTML string containing the tree's items. }
* if (!isset($variables['attributes']['class'])) {
* @see template_preprocess_menu_tree() $variables['attributes']['class'] = array();
* @ingroup themeable }
*/ $variables['attributes']['class'][] = 'menu';
function theme_menu_tree($variables) {
return '<ul class="menu">' . $variables['tree'] . '</ul>'; $variables['tree'] = $variables['tree']['#children'];
} }
/** /**
...@@ -400,8 +457,10 @@ function theme_menu_link(array $variables) { ...@@ -400,8 +457,10 @@ function theme_menu_link(array $variables) {
if ($element['#below']) { if ($element['#below']) {
$sub_menu = drupal_render($element['#below']); $sub_menu = drupal_render($element['#below']);
} }
$element['#localized_options']['set_active_class'] = TRUE; /** @var \Drupal\Core\Url $url */
$output = l($element['#title'], $element['#href'], $element['#localized_options']); $url = $element['#url'];
$url->setOption('set_active_class', TRUE);
$output = \Drupal::linkGenerator()->generateFromUrl($element['#title'], $url);
return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n"; return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
} }
...@@ -541,56 +600,28 @@ function _menu_get_links_source($name, $default) { ...@@ -541,56 +600,28 @@ function _menu_get_links_source($name, $default) {
} }
/** /**
* Returns an array of links for a navigation menu. * Builds a renderable array for a navigation menu.
* *
* @param $menu_name * @param string $menu_name
* The name of the menu. * The name of the menu.
* @param $level * @param int $level
* Optional, the depth of the menu to be returned. * Optional, the depth of the menu to be returned.
* *
* @return * @return array
* An array of links of the specified menu and level. * A renderable array.
*/ */
function menu_navigation_links($menu_name, $level = 0) { function menu_navigation_links($menu_name, $level = 0) {
// Don't even bother querying the menu table if no menu is specified. $menu_tree = \Drupal::menuTree();
if (empty($menu_name)) { $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
return array(); $parameters->setMaxDepth($level + 1);
} $tree = $menu_tree->load($menu_name, $parameters);
$manipulators = array(
// Get the menu hierarchy for the current page. array('callable' => 'menu.default_tree_manipulators:checkAccess'),
/** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
$menu_tree = \Drupal::service('menu_link.tree'); array('callable' => 'menu.default_tree_manipulators:extractSubtreeOfActiveTrail', 'args' => array($level)),
$tree = $menu_tree->buildPageData($menu_name, $level + 1); );
$tree = $menu_tree->transform($tree, $manipulators);
// Go down the active trail until the right level is reached. return $menu_tree->build($tree);
while ($level-- > 0 && $tree) {
// Loop through the current level's items until we find one that is in trail.
while ($item = array_shift($tree)) {
if ($item['link']['in_active_trail']) {
// If the item is in the active trail, we continue in the subtree.
$tree = empty($item['below']) ? array() : $item['below'];
break;
}
}
}
// Create a single level of links.
$links = array();
foreach ($tree as $item) {
if (!$item['link']['hidden']) {
$class = '';
$l = $item['link']['localized_options'];
$l['href'] = $item['link']['link_path'];
$l['title'] = $item['link']['title'];
if ($item['link']['in_active_trail']) {
$class = ' active-trail';
$l['attributes']['class'][] = 'active-trail';
}
// Keyed with the unique mlid to generate classes in links.html.twig.
$links['menu-' . $item['link']['mlid'] . $class] = $l;
}
}
return $links;
} }
/** /**
...@@ -853,17 +884,7 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { ...@@ -853,17 +884,7 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
* might have been made to the router items or menu links. * might have been made to the router items or menu links.
*/ */
function menu_cache_clear_all() { function menu_cache_clear_all() {
\Drupal::cache('data')->deleteAll(); \Drupal::cache('menu')->invalidateAll();
menu_reset_static_cache();
}
/**
* Resets the menu system static cache.
*/
function menu_reset_static_cache() {
\Drupal::entityManager()
->getStorage('menu_link')->resetCache();
drupal_static_reset('menu_link_get_preferred');
} }
/** /**
......
...@@ -2145,27 +2145,17 @@ function template_preprocess_page(&$variables) { ...@@ -2145,27 +2145,17 @@ function template_preprocess_page(&$variables) {
// Pass the main menu and secondary menu to the template as render arrays. // Pass the main menu and secondary menu to the template as render arrays.
if (!empty($variables['main_menu'])) { if (!empty($variables['main_menu'])) {
$variables['main_menu'] = array( $variables['main_menu']['#heading'] = array(
'#theme' =>'links__system_main_menu', 'text' => t('Main menu'),
'#links' => $variables['main_menu'], 'class' => array('visually-hidden'),
'#heading' => array( 'attributes' => array('id' => 'links__system_main_menu'),
'text' => t('Main menu'),
'class' => array('visually-hidden'),
'attributes' => array('id' => 'links__system_main_menu'),
),
'#set_active_class' => TRUE,
); );
} }
if (!empty($variables['secondary_menu'])) { if (!empty($variables['secondary_menu'])) {
$variables['secondary_menu'] = array( $variables['secondary_menu']['#heading'] = array(
'#theme' =>'links__system_secondary_menu', 'text' => t('Secondary menu'),
'#links' => $variables['secondary_menu'], 'class' => array('visually-hidden'),
'#heading' => array( 'attributes' => array('id' => 'links__system_secondary_menu'),
'text' => t('Secondary menu'),
'class' => array('visually-hidden'),
'attributes' => array('id' => 'links__system_secondary_menu'),
),
'#set_active_class' => TRUE,
); );
} }
...@@ -2651,6 +2641,7 @@ function drupal_common_theme() { ...@@ -2651,6 +2641,7 @@ function drupal_common_theme() {
), ),
'menu_tree' => array( 'menu_tree' => array(
'render element' => 'tree', 'render element' => 'tree',
'template' => 'menu-tree',
), ),
'menu_local_task' => array( 'menu_local_task' => array(
'render element' => 'element', 'render element' => 'element',
......
...@@ -647,4 +647,14 @@ public static function logger($channel) { ...@@ -647,4 +647,14 @@ public static function logger($channel) {
return static::$container->get('logger.factory')->get($channel); return static::$container->get('logger.factory')->get($channel);
} }
/**
* Returns the menu tree.
*
* @return \Drupal\Core\Menu\MenuLinkTreeInterface
* The menu tree.
*/
public static function menuTree() {
return static::$container->get('menu.link_tree');
}
} }
...@@ -255,10 +255,7 @@ protected function getTableMapping($table, $entity_type_id) { ...@@ -255,10 +255,7 @@ protected function getTableMapping($table, $entity_type_id) {
$mapping = $storage->getTableMapping()->getAllColumns($table); $mapping = $storage->getTableMapping()->getAllColumns($table);
} }
else { else {
// @todo Stop calling drupal_get_schema() once menu links are converted return FALSE;
// to the Entity Field API. See https://drupal.org/node/1842858.
$schema = drupal_get_schema($table);
$mapping = array_keys($schema['fields']);
} }
return array_flip($mapping); return array_flip($mapping);
} }
......
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Rebuilds the default menu links and runs menu-specific code if necessary.
*/
class MenuRouterRebuildSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Routing\RouteBuilderInterface
*/
protected $routeBuilder;
/**
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The menu link plugin manager.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
*/
protected $menuLinkManager;
/**
* Constructs the MenuRouterRebuildSubscriber object.
*
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
* The menu link plugin manager.
*/
public function __construct(LockBackendInterface $lock, MenuLinkManagerInterface $menu_link_manager) {
$this->lock = $lock;
$this->menuLinkManager = $menu_link_manager;
}
/**
* Rebuilds the menu links and deletes the local_task cache tag.
*
* @param \Symfony\Component\EventDispatcher\Event $event
* The event object.
*/
public function onRouterRebuild(Event $event) {
$this->menuLinksRebuild();
Cache::deleteTags(array('local_task' => 1));
}
/**
* Perform menu-specific rebuilding.
*/
protected function menuLinksRebuild() {
if ($this->lock->acquire(__FUNCTION__)) {
$transaction = db_transaction();
try {
// Ensure the menu links are up to date.
$this->menuLinkManager->rebuild();
// Ignore any database replicas temporarily.
db_ignore_replica();
}
catch (\Exception $e) {
$transaction->rollback();
watchdog_exception('menu', $e);
}
$this->lock->release(__FUNCTION__);
}
else {
// Wait for another request that is already doing this work.
// We choose to block here since otherwise the router item may not
// be available during routing resulting in a 404.
$this->lock->wait(__FUNCTION__);
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RoutingEvents::FINISHED][] = array('onRouterRebuild', 200);
return $events;
}
}
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
/** /**
* Rebuilds the default menu links and runs menu-specific code if necessary. * Rebuilds the router if needed at the end of the request.
*/ */
class RouterRebuildSubscriber implements EventSubscriberInterface { class RouterRebuildSubscriber implements EventSubscriberInterface {
...@@ -26,22 +26,14 @@ class RouterRebuildSubscriber implements EventSubscriberInterface { ...@@ -26,22 +26,14 @@ class RouterRebuildSubscriber implements EventSubscriberInterface {
*/ */
protected $routeBuilder; protected $routeBuilder;
/**
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/** /**
* Constructs the RouterRebuildSubscriber object. * Constructs the RouterRebuildSubscriber object.
* *
* @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
* The route builder. * The route builder.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
*/ */
public function __construct(RouteBuilderInterface $route_builder, LockBackendInterface $lock) { public function __construct(RouteBuilderInterface $route_builder) {
$this->routeBuilder = $route_builder; $this->routeBuilder = $route_builder;
$this->lock = $lock;
} }
/** /**
...@@ -55,54 +47,10 @@ public function onKernelTerminate(PostResponseEvent $event) { ...@@ -55,54 +47,10 @@ public function onKernelTerminate(PostResponseEvent $event) {
} }
/** /**
* Rebuilds the menu links and deletes the local_task cache tag. * {@inheritdoc}
*
* @param \Symfony\Component\EventDispatcher\Event $event
* The event object.
*/
public function onRouterRebuild(Event $event) {
$this->menuLinksRebuild();
Cache::deleteTags(array('local_task' => 1));
}
/**
* Perform menu-specific rebuilding.
*/
protected function menuLinksRebuild() {
if ($this->lock->acquire(__FUNCTION__)) {
$transaction = db_transaction();
try {
// Ensure the menu links are up to date.
menu_link_rebuild_defaults();
// Clear the menu cache.
menu_cache_clear_all();
// Track which menu items are expanded.
_menu_update_expanded_menus();
}
catch (\Exception $e) {
$transaction->rollback();
watchdog_exception('menu', $e);
}
$this->lock->release(__FUNCTION__);
}
else {
// Wait for another request that is already doing this work.
// We choose to block here since otherwise the router item may not
// be available during routing resulting in a 404.
$this->lock->wait(__FUNCTION__);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/ */
static function getSubscribedEvents() { static function getSubscribedEvents() {
$events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200); $events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200);
$events[RoutingEvents::FINISHED][] = array('onRouterRebuild', 200);
return $events; return $events;
} }
......
...@@ -37,22 +37,6 @@ public function getWeight() { ...@@ -37,22 +37,6 @@ public function getWeight() {
return $this->pluginDefinition['weight']; return $this->pluginDefinition['weight'];
} }
/**
* {@inheritdoc}
*/
public function getTitle() {
// Subclasses may pull in the request or specific attributes as parameters.
$options = array();
if (!empty($this->pluginDefinition['title_context'])) {
$options['context'] = $this->pluginDefinition['title_context'];
}
$args = array();
if (isset($this->pluginDefinition['title_arguments']) && $title_arguments = $this->pluginDefinition['title_arguments']) {
$args = (array) $title_arguments;
}
return $this->t($this->pluginDefinition['title'], $args, $options);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -109,16 +93,6 @@ public function isDeletable() { ...@@ -109,16 +93,6 @@ public function isDeletable() {
return (bool) $this->getDeleteRoute(); return (bool) $this->getDeleteRoute();
} }
/**
* {@inheritdoc}
*/
public function getDescription() {
if ($this->pluginDefinition['description']) {
return $this->t($this->pluginDefinition['description']);
}
return '';
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -63,6 +63,32 @@ public static function create(ContainerInterface $container, array $configuratio ...@@ -63,6 +63,32 @@ public static function create(ContainerInterface $container, array $configuratio
); );
} }
/**
* {@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} * {@inheritdoc}
*/ */
...@@ -77,11 +103,13 @@ public function isResettable() { ...@@ -77,11 +103,13 @@ public function isResettable() {
public function updateLink(array $new_definition_values, $persist) { public function updateLink(array $new_definition_values, $persist) {
// Filter the list of updates to only those that are allowed. // Filter the list of updates to only those that are allowed.
$overrides = array_intersect_key($new_definition_values, $this->overrideAllowed); $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
// Update the definition.
$this->pluginDefinition = $overrides + $this->getPluginDefinition();
if ($persist) { if ($persist) {