From 44f76c6bcffa206cf68077d943cb730e9e1e937d Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 30 Jul 2014 13:02:58 +0100 Subject: [PATCH] Revert "Issue #2301317 by pwolanin, effulgentsia, Wim Leers, dawehner, alexpott: MenuLinkNG part4: Conversion." This reverts commit fd2db9cd352b79ab6e1c40b6c5fb57a70a6530b3. --- core/core.services.yml | 9 +- core/includes/menu.inc | 183 ++++---- core/includes/theme.inc | 27 +- core/lib/Drupal.php | 10 - .../Drupal/Core/Entity/Query/Sql/Tables.php | 5 +- .../MenuRouterRebuildSubscriber.php | 98 ---- .../RouterRebuildSubscriber.php | 58 ++- core/lib/Drupal/Core/Menu/MenuLinkBase.php | 26 ++ core/lib/Drupal/Core/Menu/MenuLinkDefault.php | 32 +- core/lib/Drupal/Core/Menu/MenuLinkTree.php | 74 +-- .../lib/Drupal/Core/Utility/LinkGenerator.php | 4 +- core/modules/book/book.admin.inc | 3 +- core/modules/book/book.module | 3 +- .../content_translation.module | 4 +- .../Tests/ContentTranslationSettingsTest.php | 2 +- core/modules/dblog/dblog.module | 4 +- core/modules/editor/editor.module | 4 +- .../EntityDisplayModeController.php | 5 +- .../help/src/Controller/HelpController.php | 3 +- .../src/Tests/LanguageConfigSchemaTest.php | 8 +- .../src/Tests/LocaleLocaleLookupTest.php | 2 +- core/modules/menu_link/menu_link.info.yml | 9 + .../menu_link_content.install | 27 -- .../menu_link_content.module | 21 - .../menu_link_content.routing.yml | 3 - .../src/Form/MenuLinkContentForm.php | 29 +- core/modules/menu_ui/menu_ui.admin.inc | 37 +- core/modules/menu_ui/menu_ui.info.yml | 2 +- core/modules/menu_ui/menu_ui.links.action.yml | 5 +- core/modules/menu_ui/menu_ui.module | 420 ++++++++++------- core/modules/menu_ui/menu_ui.routing.yml | 37 +- .../menu_ui/src/Controller/MenuController.php | 46 +- .../menu_ui/src/Form/MenuDeleteForm.php | 43 +- .../menu_ui/src/Form/MenuLinkDeleteForm.php | 45 ++ .../menu_ui/src/Form/MenuLinkEditForm.php | 2 +- .../menu_ui/src/Form/MenuLinkResetForm.php | 86 +--- core/modules/menu_ui/src/MenuForm.php | 267 ++++++----- .../Plugin/Menu/LocalAction/MenuLinkAdd.php | 38 -- .../menu_ui/src/Tests/MenuCacheTagsTest.php | 38 +- .../menu_ui/src/Tests/MenuLanguageTest.php | 56 ++- .../menu_ui/src/Tests/MenuNodeTest.php | 39 +- core/modules/menu_ui/src/Tests/MenuTest.php | 429 ++++++++++-------- .../menu_ui/src/Tests/MenuWebTestBase.php | 52 +-- core/modules/node/node.api.php | 4 +- .../src/Tests/KernelTestBaseTest.php | 2 +- .../system/config/install/system.menu.yml | 1 + .../system/config/schema/system.schema.yml | 11 + .../system/src/Controller/AdminController.php | 2 +- .../src/Controller/SystemController.php | 81 ++-- .../system/src/Form/ModulesListForm.php | 70 +-- .../src/Plugin/Block/SystemMenuBlock.php | 43 +- core/modules/system/src/SystemManager.php | 95 ++-- .../system/src/Tests/Menu/BreadcrumbTest.php | 63 +-- .../system/src/Tests/Menu/LinksTest.php | 244 ++++++---- .../Menu/MenuLinkDefaultIntegrationTest.php | 70 --- .../src/Tests/Menu/MenuLinkTreeTest.php | 1 + .../src/Tests/Menu/MenuRouterRebuildTest.php | 51 +++ .../system/src/Tests/Menu/MenuRouterTest.php | 180 ++++++-- .../system/src/Tests/System/AdminTest.php | 42 +- core/modules/system/system.admin.inc | 2 +- core/modules/system/system.api.php | 6 +- core/modules/system/system.module | 87 ++-- core/modules/system/system.routing.yml | 4 +- core/modules/system/system.services.yml | 2 +- .../system/templates/menu-tree.html.twig | 40 -- .../menu_test/menu_test.links.menu.yml | 10 - .../tests/modules/menu_test/menu_test.module | 67 ++- .../test_page_test.links.menu.yml | 4 - .../src/Tests/ToolbarAdminMenuTest.php | 22 +- core/modules/toolbar/toolbar.info.yml | 1 + core/modules/toolbar/toolbar.module | 193 ++++---- .../src/Plugin/Menu/MyAccountMenuLink.php | 37 -- .../user/src/Tests/UserAccountLinksTests.php | 28 +- core/modules/user/user.links.menu.yml | 1 - core/modules/user/user.module | 23 + .../src/Plugin/Derivative/ViewsMenuLink.php | 53 --- .../Plugin/Menu/Form/ViewsMenuLinkForm.php | 83 ---- .../views/src/Plugin/Menu/ViewsMenuLink.php | 157 ------- .../views/display/DisplayPluginBase.php | 9 +- .../Plugin/views/display/PathPluginBase.php | 15 +- .../src/Tests/Plugin/DisplayPageTest.php | 5 +- .../views/src/Tests/Wizard/MenuTest.php | 19 +- core/modules/views/src/ViewExecutable.php | 11 +- core/modules/views/views.links.menu.yml | 4 - core/modules/views/views.module | 17 + core/profiles/standard/standard.info.yml | 1 - core/profiles/standard/standard.install | 13 +- .../profiles/standard/standard.links.menu.yml | 4 - .../Drupal/Tests/Core/Menu/MenuLinkMock.php | 1 + core/themes/bartik/bartik.theme | 19 +- core/themes/seven/seven.theme | 2 +- 91 files changed, 2037 insertions(+), 2158 deletions(-) delete mode 100644 core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php create mode 100644 core/modules/menu_link/menu_link.info.yml delete mode 100644 core/modules/menu_link_content/menu_link_content.install create mode 100644 core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php delete mode 100644 core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php create mode 100644 core/modules/system/config/install/system.menu.yml delete mode 100644 core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php create mode 100644 core/modules/system/src/Tests/Menu/MenuRouterRebuildTest.php delete mode 100644 core/modules/system/templates/menu-tree.html.twig delete mode 100644 core/modules/system/tests/modules/test_page_test/test_page_test.links.menu.yml delete mode 100644 core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php delete mode 100644 core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php delete mode 100644 core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php delete mode 100644 core/modules/views/src/Plugin/Menu/ViewsMenuLink.php delete mode 100644 core/modules/views/views.links.menu.yml delete mode 100644 core/profiles/standard/standard.links.menu.yml diff --git a/core/core.services.yml b/core/core.services.yml index fd9028c7d7db..562d64f4aae6 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -277,7 +277,7 @@ services: arguments: ['@menu.tree_storage', '@menu_link.static.overrides', '@module_handler'] menu.link_tree: class: Drupal\Core\Menu\MenuLinkTree - arguments: ['@menu.tree_storage', '@plugin.manager.menu.link', '@router.route_provider', '@menu.active_trail', '@controller_resolver', '@cache.menu', '@current_route_match'] + arguments: ['@menu.tree_storage', '@plugin.manager.menu.link', '@router.route_provider', '@menu.active_trail', '@controller_resolver'] menu.default_tree_manipulators: class: Drupal\Core\Menu\DefaultMenuLinkTreeManipulators arguments: ['@access_manager', '@current_user'] @@ -443,12 +443,7 @@ services: arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state'] router.rebuild_subscriber: class: Drupal\Core\EventSubscriber\RouterRebuildSubscriber - arguments: ['@router.builder'] - tags: - - { name: event_subscriber } - menu.rebuild_subscriber: - class: Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber - arguments: ['@lock', '@plugin.manager.menu.link'] + arguments: ['@router.builder', '@lock'] tags: - { name: event_subscriber } path.alias_storage: diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 17c65c171e00..8f1ce739d6b5 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -153,8 +153,8 @@ * - 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(). + * Menu items from other modules can be altered using + * hook_menu_link_defaults_alter(). * * @todo Derivatives will probably be defined for these; when they are, add * documentation here. @@ -264,56 +264,18 @@ const MENU_MAX_DEPTH = 9; /** - * @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. + * Reserved key to identify the most specific menu link for a given path. * - * 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: + * The value of this constant is a hash of the constant name. We use the hash + * so that the reserved key is over 32 characters in length and will not + * collide with allowed menu names: * @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); + * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91 * @endcode + * + * @see menu_link_get_preferred() */ +const MENU_PREFERRED_LINK = '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91'; /** * Localizes a menu link title using t() if possible. @@ -405,42 +367,23 @@ function _menu_link_translate(&$item) { * Implements template_preprocess_HOOK() for theme_menu_tree(). */ function template_preprocess_menu_tree(&$variables) { - 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'])) { - $variables['attributes'] = new Attribute($variables['tree']['#attributes']); - } - else { - $variables['attributes'] = new Attribute(); - } - if (!isset($variables['attributes']['class'])) { - $variables['attributes']['class'] = array(); - } - $variables['attributes']['class'][] = 'menu'; - $variables['tree'] = $variables['tree']['#children']; } +/** + * Returns HTML for a wrapper for a menu sub-tree. + * + * @param $variables + * An associative array containing: + * - tree: An HTML string containing the tree's items. + * + * @see template_preprocess_menu_tree() + * @ingroup themeable + */ +function theme_menu_tree($variables) { + return '<ul class="menu">' . $variables['tree'] . '</ul>'; +} + /** * Returns HTML for a menu link and submenu. * @@ -457,10 +400,8 @@ function theme_menu_link(array $variables) { if ($element['#below']) { $sub_menu = drupal_render($element['#below']); } - /** @var \Drupal\Core\Url $url */ - $url = $element['#url']; - $url->setOption('set_active_class', TRUE); - $output = \Drupal::linkGenerator()->generateFromUrl($element['#title'], $url); + $element['#localized_options']['set_active_class'] = TRUE; + $output = l($element['#title'], $element['#href'], $element['#localized_options']); return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n"; } @@ -600,28 +541,56 @@ function _menu_get_links_source($name, $default) { } /** - * Builds a renderable array for a navigation menu. + * Returns an array of links for a navigation menu. * - * @param string $menu_name + * @param $menu_name * The name of the menu. - * @param int $level + * @param $level * Optional, the depth of the menu to be returned. * - * @return array - * A renderable array. + * @return + * An array of links of the specified menu and level. */ function menu_navigation_links($menu_name, $level = 0) { - $menu_tree = \Drupal::menuTree(); - $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name); - $parameters->setMaxDepth($level + 1); - $tree = $menu_tree->load($menu_name, $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - array('callable' => 'menu.default_tree_manipulators:extractSubtreeOfActiveTrail', 'args' => array($level)), - ); - $tree = $menu_tree->transform($tree, $manipulators); - return $menu_tree->build($tree); + // Don't even bother querying the menu table if no menu is specified. + if (empty($menu_name)) { + return array(); + } + + // Get the menu hierarchy for the current page. + /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu_link.tree'); + $tree = $menu_tree->buildPageData($menu_name, $level + 1); + + // Go down the active trail until the right level is reached. + 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; } /** @@ -884,7 +853,17 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { * might have been made to the router items or menu links. */ function menu_cache_clear_all() { - \Drupal::cache('menu')->invalidateAll(); + \Drupal::cache('data')->deleteAll(); + 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'); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index f23a864f09fc..89cdc53243d8 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2145,17 +2145,27 @@ function template_preprocess_page(&$variables) { // Pass the main menu and secondary menu to the template as render arrays. if (!empty($variables['main_menu'])) { - $variables['main_menu']['#heading'] = array( - 'text' => t('Main menu'), - 'class' => array('visually-hidden'), - 'attributes' => array('id' => 'links__system_main_menu'), + $variables['main_menu'] = array( + '#theme' =>'links__system_main_menu', + '#links' => $variables['main_menu'], + '#heading' => array( + 'text' => t('Main menu'), + 'class' => array('visually-hidden'), + 'attributes' => array('id' => 'links__system_main_menu'), + ), + '#set_active_class' => TRUE, ); } if (!empty($variables['secondary_menu'])) { - $variables['secondary_menu']['#heading'] = array( - 'text' => t('Secondary menu'), - 'class' => array('visually-hidden'), - 'attributes' => array('id' => 'links__system_secondary_menu'), + $variables['secondary_menu'] = array( + '#theme' =>'links__system_secondary_menu', + '#links' => $variables['secondary_menu'], + '#heading' => array( + 'text' => t('Secondary menu'), + 'class' => array('visually-hidden'), + 'attributes' => array('id' => 'links__system_secondary_menu'), + ), + '#set_active_class' => TRUE, ); } @@ -2641,7 +2651,6 @@ function drupal_common_theme() { ), 'menu_tree' => array( 'render element' => 'tree', - 'template' => 'menu-tree', ), 'menu_local_task' => array( 'render element' => 'element', diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index e20af77f9611..21443e5b570e 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -647,14 +647,4 @@ public static function logger($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'); - } - } diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php index 23ce0205b257..facca393aa1d 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php @@ -255,7 +255,10 @@ protected function getTableMapping($table, $entity_type_id) { $mapping = $storage->getTableMapping()->getAllColumns($table); } else { - return FALSE; + // @todo Stop calling drupal_get_schema() once menu links are converted + // 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); } diff --git a/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php deleted file mode 100644 index 4b0b41f56d64..000000000000 --- a/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php +++ /dev/null @@ -1,98 +0,0 @@ -<?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; - } - -} diff --git a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php index 584b141f2bf7..87fdb872d74c 100644 --- a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php @@ -17,7 +17,7 @@ use Symfony\Component\HttpKernel\KernelEvents; /** - * Rebuilds the router if needed at the end of the request. + * Rebuilds the default menu links and runs menu-specific code if necessary. */ class RouterRebuildSubscriber implements EventSubscriberInterface { @@ -26,14 +26,22 @@ class RouterRebuildSubscriber implements EventSubscriberInterface { */ protected $routeBuilder; + /** + * @var \Drupal\Core\Lock\LockBackendInterface + */ + protected $lock; + /** * Constructs the RouterRebuildSubscriber object. * * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder * The route builder. + * @param \Drupal\Core\Lock\LockBackendInterface $lock + * The lock backend. */ - public function __construct(RouteBuilderInterface $route_builder) { + public function __construct(RouteBuilderInterface $route_builder, LockBackendInterface $lock) { $this->routeBuilder = $route_builder; + $this->lock = $lock; } /** @@ -47,10 +55,54 @@ public function onKernelTerminate(PostResponseEvent $event) { } /** - * {@inheritdoc} + * 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. + 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() { $events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200); + $events[RoutingEvents::FINISHED][] = array('onRouterRebuild', 200); return $events; } diff --git a/core/lib/Drupal/Core/Menu/MenuLinkBase.php b/core/lib/Drupal/Core/Menu/MenuLinkBase.php index 125bbf0545a4..43cb49a44940 100644 --- a/core/lib/Drupal/Core/Menu/MenuLinkBase.php +++ b/core/lib/Drupal/Core/Menu/MenuLinkBase.php @@ -37,6 +37,22 @@ public function getWeight() { 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} */ @@ -93,6 +109,16 @@ public function isDeletable() { return (bool) $this->getDeleteRoute(); } + /** + * {@inheritdoc} + */ + public function getDescription() { + if ($this->pluginDefinition['description']) { + return $this->t($this->pluginDefinition['description']); + } + return ''; + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php index 5d0f8e2e0c90..cf14df701b48 100644 --- a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php +++ b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php @@ -63,32 +63,6 @@ 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} */ @@ -103,13 +77,11 @@ public function isResettable() { 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); } + // Update the definition. + $this->pluginDefinition = $overrides + $this->getPluginDefinition(); return $this->pluginDefinition; } diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTree.php b/core/lib/Drupal/Core/Menu/MenuLinkTree.php index e5d54fe4c1d3..8d09c2c9914d 100644 --- a/core/lib/Drupal/Core/Menu/MenuLinkTree.php +++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php @@ -8,9 +8,7 @@ namespace Drupal\Core\Menu; use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Controller\ControllerResolverInterface; -use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteProviderInterface; /** @@ -46,30 +44,6 @@ class MenuLinkTree implements MenuLinkTreeInterface { */ protected $controllerResolver; - /** - * The cache backend. - * - * @var \Drupal\Core\Cache\CacheBackendInterface - */ - protected $cache; - - /** - * The current route match. - * - * @var \Drupal\Core\Routing\RouteMatchInterface - */ - protected $routeMatch; - - /** - * Stores the cached current route parameters by menu and current route match. - * - * @todo Remove this non-static caching in - * https://www.drupal.org/node/1805054. - * - * @var \Drupal\Core\Menu\MenuTreeParameters[] - */ - protected $cachedCurrentRouteParameters; - /** * Constructs a \Drupal\Core\Menu\MenuLinkTree object. * @@ -83,53 +57,31 @@ class MenuLinkTree implements MenuLinkTreeInterface { * The active menu trail service. * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver * The controller resolver. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache - * The cache backend. - * @param \Drupal\Core\Routing\RouteMatchInterface $route_match - * The current route match. */ - public function __construct(MenuTreeStorageInterface $tree_storage, MenuLinkManagerInterface $menu_link_manager, RouteProviderInterface $route_provider, MenuActiveTrailInterface $menu_active_trail, ControllerResolverInterface $controller_resolver, CacheBackendInterface $cache, RouteMatchInterface $route_match) { + 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; - // @todo Remove these two in https://www.drupal.org/node/1805054. - $this->cache = $cache; - $this->routeMatch = $route_match; } /** * {@inheritdoc} */ public function getCurrentRouteMenuTreeParameters($menu_name) { - $route_parameters = $this->routeMatch->getRawParameters()->all(); - ksort($route_parameters); - $cid = 'current-route-parameters:' . $menu_name . ':route:' . $this->routeMatch->getRouteName() . ':route_parameters:' . serialize($route_parameters); - - if (!isset($this->cachedCurrentRouteParameters[$menu_name])) { - $cache = $this->cache->get($cid); - if ($cache && $cache->data) { - $parameters = $cache->data; - } - else { - $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)); - - $this->cache->set($cid, $parameters, CacheBackendInterface::CACHE_PERMANENT, array('menu' => $menu_name)); - } - $this->cachedCurrentRouteParameters[$menu_name] = $parameters; - } - - return $this->cachedCurrentRouteParameters[$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; } /** diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index d3093ac093a7..4912a052abd7 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -99,9 +99,7 @@ public function generateFromUrl($text, Url $url) { // drupal.active-link library know the path in a standardized manner. if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) { // @todo System path is deprecated - use the route name and parameters. - $system_path = $url->getInternalPath(); - // Special case for the front page. - $variables['options']['attributes']['data-drupal-link-system-path'] = $system_path == '' ? '<front>' : $system_path; + $variables['options']['attributes']['data-drupal-link-system-path'] = $url->getInternalPath(); } } diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc index 6a3edc16f528..9a558698a8f0 100644 --- a/core/modules/book/book.admin.inc +++ b/core/modules/book/book.admin.inc @@ -6,7 +6,6 @@ */ use Drupal\Component\Utility\SafeMarkup; -use Drupal\book\BookManager; use Drupal\Core\Render\Element; /** @@ -88,7 +87,7 @@ function theme_book_admin_table($variables) { 'subgroup' => 'book-pid', 'source' => 'book-nid', 'hidden' => TRUE, - 'limit' => BookManager::BOOK_MAX_DEPTH - 2, + 'limit' => MENU_MAX_DEPTH - 2, ), array( 'action' => 'order', diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 1177ebeabb10..ba00049b52a1 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -5,7 +5,6 @@ * Allows users to create and organize related content in an outline. */ -use Drupal\book\BookManager; use Drupal\book\BookManagerInterface; use Drupal\Component\Utility\String; use Drupal\Core\Entity\EntityInterface; @@ -124,7 +123,7 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$ if ($context['view_mode'] == 'full' && node_is_page($node)) { $child_type = \Drupal::config('book.settings')->get('child_type'); $access_controller = \Drupal::entityManager()->getAccessController('node'); - if (($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines')) && $access_controller->createAccess($child_type) && $node->isPublished() && $node->book['depth'] < BookManager::BOOK_MAX_DEPTH) { + if (($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines')) && $access_controller->createAccess($child_type) && $node->isPublished() && $node->book['depth'] < MENU_MAX_DEPTH) { $links['book_add_child'] = array( 'title' => t('Add child page'), 'href' => 'node/add/' . $child_type, diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index 21144a5ff5f0..4fbd597b1d3d 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -203,9 +203,9 @@ function content_translation_entity_operation_alter(array &$operations, \Drupal\ } /** - * Implements hook_menu_links_discovered_alter(). + * Implements hook_menu_link_defaults_alter(). */ -function content_translation_menu_links_discovered_alter(array &$links) { +function content_translation_menu_link_defaults_alter(array &$links) { // Clarify where translation settings are located. $links['language.content_settings_page']['title'] = 'Content language and translation'; $links['language.content_settings_page']['description'] = 'Configure language and translation support for content.'; diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php index 76fa6be2e07e..556e419dfead 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php @@ -44,7 +44,7 @@ function setUp() { * Tests that the settings UI works as expected. */ function testSettingsUI() { - // Check for the content_translation_menu_links_discovered_alter() changes. + // Check for the content_translation_menu_link_defaults_alter() changes. $this->drupalGet('admin/config'); $this->assertLink('Content language and translation'); $this->assertText('Configure language and translation support for content.'); diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module index b2068793e346..d6e20ff36c57 100644 --- a/core/modules/dblog/dblog.module +++ b/core/modules/dblog/dblog.module @@ -37,9 +37,9 @@ function dblog_help($route_name, RouteMatchInterface $route_match) { } /** - * Implements hook_menu_links_discovered_alter(). + * Implements hook_menu_link_defaults_alter(). */ -function dblog_menu_links_discovered_alter(&$links) { +function dblog_menu_link_defaults_alter(&$links) { if (\Drupal::moduleHandler()->moduleExists('search')) { $links['dblog.search'] = array( 'title' => 'Top search phrases', diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index 40c1eb741351..5aa79298c452 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -40,12 +40,12 @@ function editor_help($route_name, RouteMatchInterface $route_match) { } /** - * Implements hook_menu_links_discovered_alter(). + * Implements hook_menu_link_defaults_alter(). * * Rewrites the menu entries for filter module that relate to the configuration * of text editors. */ -function editor_menu_links_discovered_alter(array &$links) { +function editor_menu_link_defaults_alter(array &$links) { $links['filter.admin_overview']['title'] = 'Text formats and editors'; $links['filter.admin_overview']['description'] = 'Configure how user-contributed content is filtered and formatted, as well as the text editor user interface (WYSIWYGs or toolbars).'; } diff --git a/core/modules/entity/src/Controller/EntityDisplayModeController.php b/core/modules/entity/src/Controller/EntityDisplayModeController.php index af31044a2875..f0db9420f03b 100644 --- a/core/modules/entity/src/Controller/EntityDisplayModeController.php +++ b/core/modules/entity/src/Controller/EntityDisplayModeController.php @@ -8,7 +8,6 @@ namespace Drupal\entity\Controller; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Url; /** * Provides methods for entity display mode routes. @@ -27,7 +26,7 @@ public function viewModeTypeSelection() { if ($entity_type->isFieldable() && $entity_type->hasViewBuilderClass()) { $entity_types[$entity_type_id] = array( 'title' => $entity_type->getLabel(), - 'url' => new Url('entity.view_mode_add_type', array('entity_type_id' => $entity_type_id)), + 'link_path' => 'admin/structure/display-modes/view/add/' . $entity_type_id, 'localized_options' => array(), ); } @@ -50,7 +49,7 @@ public function formModeTypeSelection() { if ($entity_type->isFieldable() && $entity_type->hasFormClasses()) { $entity_types[$entity_type_id] = array( 'title' => $entity_type->getLabel(), - 'url' => new Url('entity.form_mode_add_type', array('entity_type_id' => $entity_type_id)), + 'link_path' => 'admin/structure/display-modes/form/add/' . $entity_type_id, 'localized_options' => array(), ); } diff --git a/core/modules/help/src/Controller/HelpController.php b/core/modules/help/src/Controller/HelpController.php index 437c3a86e0ea..26a3160b623e 100644 --- a/core/modules/help/src/Controller/HelpController.php +++ b/core/modules/help/src/Controller/HelpController.php @@ -125,7 +125,8 @@ public function helpPage($name) { if (!empty($admin_tasks)) { $links = array(); foreach ($admin_tasks as $task) { - $link = $task['url']->toArray(); + $link = $task['localized_options']; + $link['href'] = $task['link_path']; $link['title'] = $task['title']; $links[] = $link; } diff --git a/core/modules/language/src/Tests/LanguageConfigSchemaTest.php b/core/modules/language/src/Tests/LanguageConfigSchemaTest.php index 28fcad0a62d0..e4862bcb0a7f 100644 --- a/core/modules/language/src/Tests/LanguageConfigSchemaTest.php +++ b/core/modules/language/src/Tests/LanguageConfigSchemaTest.php @@ -24,7 +24,7 @@ class LanguageConfigSchemaTest extends WebTestBase { * * @var array */ - public static $modules = array('language', 'menu_link_content'); + public static $modules = array('language'); /** * A user with administrative permissions. @@ -55,8 +55,8 @@ function testValidLanguageConfigSchema() { $settings_path = 'admin/config/regional/content-language'; // Enable translation for menu link. - $edit['entity_types[menu_link_content]'] = TRUE; - $edit['settings[menu_link_content][menu_link_content][settings][language][language_show]'] = TRUE; + $edit['entity_types[menu_link]'] = TRUE; + $edit['settings[menu_link][menu_link][settings][language][language_show]'] = TRUE; // Enable translation for user. $edit['entity_types[user]'] = TRUE; @@ -67,7 +67,7 @@ function testValidLanguageConfigSchema() { $config_data = \Drupal::config('language.settings')->get(); // Make sure configuration saved correctly. - $this->assertTrue($config_data['entities']['menu_link_content']['menu_link_content']['language']['default_configuration']['language_show']); + $this->assertTrue($config_data['entities']['menu_link']['menu_link']['language']['default_configuration']['language_show']); $this->assertConfigSchema(\Drupal::service('config.typed'), 'language.settings', $config_data); } diff --git a/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php index 4f4c3d5985a5..b25d639abc76 100644 --- a/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php +++ b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php @@ -22,7 +22,7 @@ class LocaleLocaleLookupTest extends WebTestBase { * * @var array */ - public static $modules = array('locale'); + public static $modules = array('locale', 'menu_link'); /** * Tests hasTranslation(). diff --git a/core/modules/menu_link/menu_link.info.yml b/core/modules/menu_link/menu_link.info.yml new file mode 100644 index 000000000000..bc8e62beeb60 --- /dev/null +++ b/core/modules/menu_link/menu_link.info.yml @@ -0,0 +1,9 @@ +name: Menu Link +type: module +description: Provides menu links. +package: Core +version: VERSION +core: 8.x +# @todo Menu links functionality has been moved from system.module and menu.inc +# to this module, so make it required until everything is moved over. +required: TRUE diff --git a/core/modules/menu_link_content/menu_link_content.install b/core/modules/menu_link_content/menu_link_content.install deleted file mode 100644 index 31fa93f0a2d6..000000000000 --- a/core/modules/menu_link_content/menu_link_content.install +++ /dev/null @@ -1,27 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the menu_link_content module. - */ - -/** - * Implements hook_uninstall(). - */ -function menu_link_content_uninstall() { - // Find all the entities and then call the manager and delete all the plugins. - $query = \Drupal::entityQueryAggregate('menu_link_content'); - $query->groupBy('uuid'); - $result = $query->execute(); - $uuids = array(); - foreach ($result as $row) { - $uuids[] = $row['uuid']; - } - - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - foreach ($uuids as $uuid) { - // Manually build the plugin ID, and remove it from the menu tree. - $menu_link_manager->removeDefinition("menu_link_content:$uuid", FALSE); - } -} diff --git a/core/modules/menu_link_content/menu_link_content.module b/core/modules/menu_link_content/menu_link_content.module index 6d33dcea2108..25dfb2692151 100644 --- a/core/modules/menu_link_content/menu_link_content.module +++ b/core/modules/menu_link_content/menu_link_content.module @@ -5,29 +5,8 @@ * Allows administrators to create custom menu links. */ -use Drupal\Core\Routing\RouteMatchInterface; use Drupal\system\MenuInterface; -/** - * Implements hook_help(). - */ -function menu_link_content_help($route_name, RouteMatchInterface $route_match) { - switch ($route_name) { - case 'help.page.menu_link_content': - $output = ''; - $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t('The Custom Menu Links module allows users to create menu links. These links can be translated if multiple languages are used for the site.'); - if (\Drupal::moduleHandler()->moduleExists('menu_ui')) { - $output .= ' ' . t('It is required by the Menu UI module, which provides an interface for managing menus and menu links. See the <a href="!menu-help">Menu UI module help page</a> for more information.', array('!menu-help' => \Drupal::url('help.page', array('name' => 'menu_ui')))); - } - else { - $output .= ' ' . t('If you enable the Menu UI module, it provides an interface for managing menus and menu links.'); - } - $output .= '</p>'; - return $output; - } -} - /** * Implements hook_menu_delete(). */ diff --git a/core/modules/menu_link_content/menu_link_content.routing.yml b/core/modules/menu_link_content/menu_link_content.routing.yml index b64b36244bfc..f96b22d468a1 100644 --- a/core/modules/menu_link_content/menu_link_content.routing.yml +++ b/core/modules/menu_link_content/menu_link_content.routing.yml @@ -11,9 +11,6 @@ menu_link_content.link_edit: defaults: _entity_form: 'menu_link_content.default' _title: 'Edit menu link' - options: - # @todo Remove once https://www.drupal.org/node/2310475 is in. - _admin_route: TRUE requirements: _entity_access: 'menu_link_content.update' diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php index 7b5d6ef96f2a..c3f63956253f 100644 --- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php @@ -144,6 +144,13 @@ public function setMenuLinkInstance(MenuLinkInterface $menu_link) { * {@inheritdoc} */ public function buildConfigurationForm(array $form, array &$form_state) { + return $this->buildEditForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function buildEditForm(array &$form, array &$form_state) { $this->setOperation('default'); $this->init($form_state); @@ -157,13 +164,29 @@ public function validateConfigurationForm(array &$form, array &$form_state) { $this->doValidate($form, $form_state); } + /** + * {@inheritdoc} + */ + public function validateEditForm(array &$form, array &$form_state) { + $this->doValidate($form, $form_state); + } + /** * {@inheritdoc} */ public function submitConfigurationForm(array &$form, array &$form_state) { + return $this->submitEditForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitEditForm(array &$form, array &$form_state) { // Remove button and internal Form API values from submitted values. - parent::submit($form, $form_state); - $this->save($form, $form_state); + form_state_values_clean($form_state); + $this->entity = $this->buildEntity($form, $form_state); + $this->entity->save(); + return $form_state; } /** @@ -211,7 +234,7 @@ protected function extractUrl($url) { */ public function extractFormValues(array &$form, array &$form_state) { $new_definition = array(); - $new_definition['expanded'] = !empty($form_state['values']['expanded']['value']) ? 1 : 0; + $new_definition['expanded'] = !empty($form_state['values']['expanded']) ? 1 : 0; $new_definition['hidden'] = empty($form_state['values']['enabled']) ? 1 : 0; list($menu_name, $parent) = explode(':', $form_state['values']['menu_parent'], 2); if (!empty($menu_name)) { diff --git a/core/modules/menu_ui/menu_ui.admin.inc b/core/modules/menu_ui/menu_ui.admin.inc index f23093f1e0f4..0d7a0fdda8a8 100644 --- a/core/modules/menu_ui/menu_ui.admin.inc +++ b/core/modules/menu_ui/menu_ui.admin.inc @@ -5,19 +5,15 @@ * Administrative page callbacks for Menu UI module. */ -use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Render\Element; /** * Returns HTML for the menu overview form into a table. * - * @param array $variables + * @param $variables * An associative array containing: * - form: A render element representing the form. * - * @return string - * The themed HTML. - * * @ingroup themeable */ function theme_menu_overview_form($variables) { @@ -31,28 +27,27 @@ function theme_menu_overview_form($variables) { ); $rows = array(); - foreach (Element::children($form) as $id) { - if (isset($form[$id]['#item'])) { - $element = &$form[$id]; + foreach (Element::children($form) as $mlid) { + if (isset($form[$mlid]['hidden'])) { + $element = &$form[$mlid]; // Add special classes to be used for tabledrag.js. - $element['parent']['#attributes']['class'] = array('menu-parent'); - $element['id']['#attributes']['class'] = array('menu-id'); + $element['plid']['#attributes']['class'] = array('menu-plid'); + $element['mlid']['#attributes']['class'] = array('menu-mlid'); $element['weight']['#attributes']['class'] = array('menu-weight'); - // Change the parent field to a hidden. This allows any value but hides - // the field. - $element['parent']['#type'] = 'hidden'; + // Change the parent field to a hidden. This allows any value but hides the field. + $element['plid']['#type'] = 'hidden'; $indent = array( '#theme' => 'indentation', - '#size' => $element['#item']->depth - 1, + '#size' => $element['#item']['depth'] - 1, ); $row = array(); - $row[] = SafeMarkup::set(drupal_render($indent) . drupal_render($element['title'])); - $row[] = array('data' => drupal_render($element['enabled']), 'class' => array('checkbox', 'menu-enabled')); - $row[] = SafeMarkup::set(drupal_render($element['weight']) . drupal_render($element['parent']) . drupal_render($element['id'])); + $row[] = drupal_render($indent) . drupal_render($element['title']); + $row[] = array('data' => drupal_render($element['hidden']), 'class' => array('checkbox', 'menu-enabled')); + $row[] = drupal_render($element['weight']) . drupal_render($element['plid']) . drupal_render($element['mlid']); $row[] = drupal_render($element['operations']); $row = array_merge(array('data' => $row), $element['#attributes']); @@ -76,11 +71,11 @@ function theme_menu_overview_form($variables) { array( 'action' => 'match', 'relationship' => 'parent', - 'group' => 'menu-parent', - 'subgroup' => 'menu-parent', - 'source' => 'menu-id', + 'group' => 'menu-plid', + 'subgroup' => 'menu-plid', + 'source' => 'menu-mlid', 'hidden' => TRUE, - 'limit' => \Drupal::menuTree()->maxDepth() - 1, + 'limit' => MENU_MAX_DEPTH - 1, ), array( 'action' => 'order', diff --git a/core/modules/menu_ui/menu_ui.info.yml b/core/modules/menu_ui/menu_ui.info.yml index f3bf1beb772c..26611f17dbc1 100644 --- a/core/modules/menu_ui/menu_ui.info.yml +++ b/core/modules/menu_ui/menu_ui.info.yml @@ -6,4 +6,4 @@ version: VERSION core: 8.x configure: menu_ui.overview_page dependencies: - - menu_link_content + - menu_link diff --git a/core/modules/menu_ui/menu_ui.links.action.yml b/core/modules/menu_ui/menu_ui.links.action.yml index af8716f96b53..08348211f130 100644 --- a/core/modules/menu_ui/menu_ui.links.action.yml +++ b/core/modules/menu_ui/menu_ui.links.action.yml @@ -1,7 +1,6 @@ -menu_ui.link_add: - route_name: menu_link_content.link_add +menu_ui_link_add: + route_name: menu_ui.link_add title: 'Add link' - class: \Drupal\menu_ui\Plugin\Menu\LocalAction\MenuLinkAdd appears_on: - menu_ui.menu_edit diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module index 79cd051e5b61..70b5b5944e54 100644 --- a/core/modules/menu_ui/menu_ui.module +++ b/core/modules/menu_ui/menu_ui.module @@ -10,13 +10,13 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\block\BlockPluginInterface; -use Drupal\Core\Menu\MenuLinkInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\menu_link_content\Entity\MenuLinkContent; use Drupal\node\NodeTypeInterface; use Drupal\system\Entity\Menu; use Symfony\Component\HttpFoundation\JsonResponse; +use Drupal\menu_link\Entity\MenuLink; +use Drupal\menu_link\MenuLinkStorage; use Drupal\node\NodeInterface; /** @@ -72,11 +72,31 @@ function menu_ui_entity_type_build(array &$entity_types) { ->setFormClass('edit', 'Drupal\menu_ui\MenuForm') ->setFormClass('delete', 'Drupal\menu_ui\Form\MenuDeleteForm') ->setListBuilderClass('Drupal\menu_ui\MenuListBuilder') - ->setLinkTemplate('add-form', 'menu_ui.menu_add') + ->setLinkTemplate('add-form', 'menu_ui.link_add') ->setLinkTemplate('delete-form', 'menu_ui.delete_menu') ->setLinkTemplate('edit-form', 'menu_ui.menu_edit'); + + $entity_types['menu_link'] + ->setFormClass('delete', 'Drupal\menu_ui\Form\MenuLinkDeleteForm') + ->setFormClass('reset', 'Drupal\menu_ui\Form\MenuLinkResetForm') + ->setLinkTemplate('delete-form', 'menu_ui.link_delete'); } +/** + * Implements hook_entity_bundle_info(). + */ +function menu_ui_entity_bundle_info() { + $bundles = array(); + $config_names = \Drupal::configFactory()->listAll('system.menu.'); + foreach ($config_names as $config_name) { + $config = \Drupal::config($config_name); + $bundles['menu_link'][$config->get('id')] = array( + 'label' => $config->get('label'), + ); + } + + return $bundles; +} /** * Implements hook_theme(). @@ -99,6 +119,23 @@ function menu_ui_menu_insert(Menu $menu) { if (\Drupal::moduleHandler()->moduleExists('block')) { \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); } + + if ($menu->isSyncing()) { + return; + } + + // Make sure the menu is present in the active menus variable so that its + // items may appear in the menu active trail. + // See menu_set_active_menu_names(). + $config = \Drupal::config('system.menu'); + + $active_menus = $config->get('active_menus_default') ?: array_keys(menu_ui_get_menus()); + if (!in_array($menu->id(), $active_menus)) { + $active_menus[] = $menu->id(); + $config + ->set('active_menus_default', $active_menus) + ->save(); + } } /** @@ -117,9 +154,20 @@ function menu_ui_menu_update(Menu $menu) { */ function menu_ui_menu_predelete(Menu $menu) { // Delete all links from the menu. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $menu_link_manager->deleteLinksInMenu($menu->id()); + menu_delete_links($menu->id()); + + // Remove menu from active menus variable. + $config = \Drupal::config('system.menu'); + $active_menus = $config->get('active_menus_default') ?: array_keys(menu_ui_get_menus()); + if (in_array($menu->id(), $active_menus)) { + $active_menus = array_diff($active_menus, array($menu->id())); + // Prevent the gap left by the removed menu from causing array indices to + // be saved. + $active_menus = array_values($active_menus); + $config + ->set('active_menus_default', $active_menus) + ->save(); + } } /** @@ -134,6 +182,103 @@ function menu_ui_menu_delete(Menu $menu) { } } +/** + * Returns a list of menu links that are valid possible parents for the given + * menu link. + * + * @param array $menus + * An array of menu names and titles, such as from menu_ui_get_menus(). + * @param \Drupal\menu_link\Entity\MenuLink $menu_link + * The menu link for which to generate a list of parents. + * If $menu_link->id() == 0 then the complete tree is returned. + * @param string $type + * The node type for which to generate a list of parents. + * If $item itself is a node type then $type is ignored. + * + * @return array + * An array of menu link titles keyed by a string containing the menu name and + * mlid. The list excludes the given item and its children. + * + * @todo This has to be turned into a #process form element callback. The + * 'override_parent_selector' variable is entirely superfluous. + */ +function menu_ui_parent_options(array $menus, MenuLink $menu_link = NULL, $type = NULL) { + // The menu_links table can be practically any size and we need a way to + // allow contrib modules to provide more scalable pattern choosers. + // hook_form_alter is too late in itself because all the possible parents are + // retrieved here, unless override_parent_selector is set to TRUE. + if (\Drupal::config('menu_ui.settings')->get('override_parent_selector')) { + return array(); + } + + if (!$menu_link) { + $menu_link = entity_create('menu_link', array('mlid' => 0)); + } + + $available_menus = array(); + if (!$type) { + // If no node type is set, use all menus given to this function. + $available_menus = $menus; + } + else { + // If a node type is set, use all available menus for this type. + $type_menus = \Drupal::config("menu.entity.node.$type")->get('available_menus'); + foreach ($type_menus as $menu) { + $available_menus[$menu] = $menu; + } + } + + return _menu_ui_get_options($menus, $available_menus, $menu_link); +} + +/** + * Helper function to get the items of the given menu. + */ +function _menu_ui_get_options($menus, $available_menus, $item) { + // If the item has children, there is an added limit to the depth of valid parents. + if (isset($item['parent_depth_limit'])) { + $limit = $item['parent_depth_limit']; + } + else { + $limit = _menu_ui_parent_depth_limit($item); + } + + /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu_link.tree'); + + $options = array(); + foreach ($menus as $menu_name => $title) { + if (isset($available_menus[$menu_name])) { + $tree = $menu_tree->buildAllData($menu_name, NULL); + $options[$menu_name . ':0'] = '<' . $title . '>'; + _menu_ui_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit); + } + } + return $options; +} + +/** + * Recursive helper function for menu_ui_parent_options(). + */ +function _menu_ui_parents_recurse($tree, $menu_name, $indent, &$options, $exclude, $depth_limit) { + foreach ($tree as $data) { + if ($data['link']['depth'] > $depth_limit) { + // Don't iterate through any links on this level. + break; + } + if ($data['link']['mlid'] != $exclude && $data['link']['hidden'] >= 0) { + $title = $indent . ' ' . truncate_utf8($data['link']['title'], 30, TRUE, FALSE); + if ($data['link']['hidden']) { + $title .= ' (' . t('disabled') . ')'; + } + $options[$menu_name . ':' . $data['link']['mlid']] = $title; + if ($data['below']) { + _menu_ui_parents_recurse($data['below'], $menu_name, $indent . '--', $options, $exclude, $depth_limit); + } + } + } +} + /** * Implements hook_block_view_BASE_BLOCK_ID_alter() for 'system_menu_block'. */ @@ -141,7 +286,7 @@ function menu_ui_block_view_system_menu_block_alter(array &$build, BlockPluginIn // Add contextual links for system menu blocks. $menus = menu_list_system_menus(); $menu_name = $block->getDerivativeId(); - if (isset($menus[$menu_name]) && isset($build['content'])) { + if (isset($menus[$menu_name])) { $build['#contextual_links']['menu'] = array( 'route_parameters' => array('menu' => $menu_name), ); @@ -171,7 +316,7 @@ function menu_ui_node_type_insert(NodeTypeInterface $type) { } \Drupal::config('menu.entity.node.' . $type->id()) ->set('available_menus', array('main')) - ->set('parent', 'main:') + ->set('parent', 'main:0') ->save(); } @@ -189,35 +334,25 @@ function menu_ui_node_type_delete(NodeTypeInterface $type) { * Helper for hook_ENTITY_TYPE_insert() and hook_ENTITY_TYPE_update() for nodes. */ function menu_ui_node_save(EntityInterface $node) { - if (!empty($node->menu)) { - /** @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface $entity */ - $definition = $node->menu; - if (trim($definition['title'])) { - if (!empty($definition['entity_id'])) { - $entity = entity_load('menu_link_content', $definition['entity_id']); - $entity->hidden->value = 0; - $entity->title->value = trim($definition['title']); - $entity->description->value = trim($definition['description']); - $entity->menu_name->value = $definition['menu_name']; - $entity->parent->value = $definition['parent']; - $entity->weight->value = isset($definition['weight']) ? $definition['weight'] : 0; + if (isset($node->menu)) { + $link = &$node->menu; + if (empty($link['enabled'])) { + if (!$link->isNew()) { + menu_link_delete($link['mlid']); + } + } + elseif (trim($link['link_title'])) { + $link['link_title'] = trim($link['link_title']); + $link['link_path'] = 'node/' . $node->id(); + if (trim($link['description'])) { + $link['options']['attributes']['title'] = trim($link['description']); } else { - // Create a new menu_link_content entity. - $entity = entity_create('menu_link_content', array( - 'title' => trim($definition['title']), - 'description' => trim($definition['description']), - 'route_name' => 'node.view', - 'route_parameters' => array('node' => $node->id()), - 'menu_name' => $definition['menu_name'], - 'parent' => $definition['parent'], - 'weight' => isset($definition['weight']) ? $definition['weight'] : 0, - 'hidden' => 0, - 'bundle' => 'menu_link_content', - 'langcode' => $node->getUntranslated()->language()->id, - )); + // If the description field was left empty, remove the title attribute + // from the menu link. + unset($link['options']['attributes']['title']); } - if (!$entity->save()) { + if (!menu_link_save($link)) { drupal_set_message(t('There was an error saving the menu link.'), 'error'); } } @@ -228,17 +363,14 @@ function menu_ui_node_save(EntityInterface $node) { * Implements hook_ENTITY_TYPE_predelete() for node entities. */ function menu_ui_node_predelete(EntityInterface $node) { - // Delete all MenuLinkContent links that point to this node. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $result = $menu_link_manager->loadLinksByRoute('node.view', array('node' => $node->id())); + // Delete all Menu UI module links that point to this node. + $query = \Drupal::entityQuery('menu_link') + ->condition('link_path', 'node/' . $node->id()) + ->condition('module', 'menu'); + $result = $query->execute(); if (!empty($result)) { - foreach ($result as $id => $instance) { - if ($instance->isDeletable() && strpos($id, 'menu_link_content:') === 0) { - $instance->deleteLink(); - } - } + menu_link_delete_multiple($result); } } @@ -246,68 +378,66 @@ function menu_ui_node_predelete(EntityInterface $node) { * Implements hook_node_prepare_form(). */ function menu_ui_node_prepare_form(NodeInterface $node, $operation, array &$form_state) { - if (empty($form_state['menu_link_definition'])) { + if (empty($node->menu)) { // Prepare the node for the edit form so that $node->menu always exists. $node_type_config = \Drupal::config('menu.entity.node.' . $node->getType()); $menu_name = strtok($node_type_config->get('parent'), ':'); - $definition = FALSE; + $menu_link = FALSE; if ($node->id()) { - $id = FALSE; + $mlid = FALSE; // Give priority to the default menu $type_menus = $node_type_config->get('available_menus'); if (in_array($menu_name, $type_menus)) { - $query = \Drupal::entityQuery('menu_link_content') - ->condition('route_name', 'node.view') - ->condition('route_parameters', serialize(array('node' => $node->id()))) + $query = \Drupal::entityQuery('menu_link') + ->condition('link_path', 'node/' . $node->id()) ->condition('menu_name', $menu_name) - ->sort('id', 'ASC') + ->condition('module', 'menu_ui') + ->sort('mlid', 'ASC') ->range(0, 1); $result = $query->execute(); - $id = (!empty($result)) ? reset($result) : FALSE; + $mlid = (!empty($result)) ? reset($result) : FALSE; } // Check all allowed menus if a link does not exist in the default menu. - if (!$id && !empty($type_menus)) { - $query = \Drupal::entityQuery('menu_link_content') - ->condition('route_name', 'node.view') - ->condition('route_parameters', serialize(array('node' => $node->id()))) + if (!$mlid && !empty($type_menus)) { + $query = \Drupal::entityQuery('menu_link') + ->condition('link_path', 'node/' . $node->id()) ->condition('menu_name', array_values($type_menus), 'IN') - ->sort('id', 'ASC') + ->condition('module', 'menu_ui') + ->sort('mlid', 'ASC') ->range(0, 1); $result = $query->execute(); - $id = (!empty($result)) ? reset($result) : FALSE; + $mlid = (!empty($result)) ? reset($result) : FALSE; } - if ($id) { - $menu_link = MenuLinkContent::load($id); - $definition = array( - 'entity_id' => $menu_link->id(), - 'id' => $menu_link->getPluginId(), - 'title' => $menu_link->getTitle(), - 'description' => $menu_link->getDescription(), - 'menu_name' => $menu_link->getMenuName(), - 'parent' => $menu_link->getParentId(), - 'weight' => $menu_link->getWeight(), - ); + if ($mlid) { + $menu_link = menu_link_load($mlid); } } - if (!$definition) { - $definition = array( - 'entity_id' => 0, - 'id' => '', - 'title' => '', - 'description' => '', + if (!$menu_link) { + $menu_link = entity_create('menu_link', array( + 'mlid' => 0, + 'plid' => 0, 'menu_name' => $menu_name, - 'parent' => '', - 'weight' => 0, - ); + )); } // Set default values. - $form_state['menu_link_definition'] = $definition; + $node->menu = $menu_link; + } + // Find the depth limit for the parent select. + if (!isset($node->menu['parent_depth_limit'])) { + $node->menu['parent_depth_limit'] = _menu_ui_parent_depth_limit($node->menu); } } +/** + * Find the depth limit for items in the parent select. + */ +function _menu_ui_parent_depth_limit($item) { + return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($item) : 0); +} + /** * Implements hook_form_BASE_FORM_ID_alter() for node_form. * @@ -319,25 +449,11 @@ function menu_ui_form_node_form_alter(&$form, $form_state) { // Generate a list of possible parents (not including this link or descendants). // @todo This must be handled in a #process handler. $node = $form_state['controller']->getEntity(); - $definition = $form_state['menu_link_definition']; + $link = $node->menu; $type = $node->getType(); - /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */ - $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); - $menu_names = menu_ui_get_menus(); - $type_menus = \Drupal::config("menu.entity.node.$type")->get('available_menus'); - $available_menus = array(); - foreach ($type_menus as $menu) { - $available_menus[$menu] = $menu_names[$menu]; - } - if ($definition['id']) { - $default = $definition['menu_name'] . ':' . $definition['parent']; - } - else { - $default = \Drupal::config('menu.entity.node.'.$type)->get('parent'); - } - $parent_element = $menu_parent_selector->parentSelectElement($default, $definition['id'], $available_menus); + $options = menu_ui_parent_options(menu_ui_get_menus(), $link, $type); // If no possible parent menu items were found, there is nothing to display. - if (empty($parent_element)) { + if (empty($options)) { return; } @@ -345,7 +461,7 @@ function menu_ui_form_node_form_alter(&$form, $form_state) { '#type' => 'details', '#title' => t('Menu settings'), '#access' => \Drupal::currentUser()->hasPermission('administer menu'), - '#open' => (bool) $definition['id'], + '#open' => !empty($link['link_title']), '#group' => 'advanced', '#attached' => array( 'library' => array('menu/drupal.menu'), @@ -357,7 +473,7 @@ function menu_ui_form_node_form_alter(&$form, $form_state) { $form['menu']['enabled'] = array( '#type' => 'checkbox', '#title' => t('Provide a menu link'), - '#default_value' => (int) (bool) $definition['id'], + '#default_value' => (int) (bool) $link['mlid'], ); $form['menu']['link'] = array( '#type' => 'container', @@ -370,32 +486,57 @@ function menu_ui_form_node_form_alter(&$form, $form_state) { ); // Populate the element with the link data. - foreach (array('id', 'entity_id') as $key) { - $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $definition[$key]); + foreach (array('mlid', 'module', 'hidden', 'has_children', 'customized', 'options', 'expanded', 'hidden', 'parent_depth_limit') as $key) { + $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $link[$key]); } - $form['menu']['link']['title'] = array( + $form['menu']['link']['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), - '#default_value' => $definition['title'], + '#default_value' => $link['link_title'], ); $form['menu']['link']['description'] = array( '#type' => 'textarea', '#title' => t('Description'), - '#default_value' => $definition['description'], + '#default_value' => isset($link['options']['attributes']['title']) ? $link['options']['attributes']['title'] : '', '#rows' => 1, '#description' => t('Shown when hovering over the menu link.'), ); - $form['menu']['link']['menu_parent'] = $parent_element; - $form['menu']['link']['menu_parent']['#title'] = t('Parent item'); - $form['menu']['link']['menu_parent']['#attributes']['class'][] = 'menu-parent-select'; + if ($link['mlid']) { + $default = $link['menu_name'] . ':' . $link['plid']; + } + else { + $default = \Drupal::config('menu.entity.node.'.$type)->get('parent'); + } + // If the current parent menu item is not present in options, use the first + // available option as default value. + // @todo User should not be allowed to access menu link settings in such a + // case. + if (!isset($options[$default])) { + $array = array_keys($options); + $default = reset($array); + } + $form['menu']['link']['parent'] = array( + '#type' => 'select', + '#title' => t('Parent item'), + '#default_value' => $default, + '#options' => $options, + '#attributes' => array('class' => array('menu-parent-select')), + ); + // Get number of items in menu so the weight selector is sized appropriately. + $delta = entity_get_controller('menu_link')->countMenuLinks($link->menu_name); + if ($delta < 50) { + // Old hardcoded value + $delta = 50; + } $form['menu']['link']['weight'] = array( - '#type' => 'number', + '#type' => 'weight', '#title' => t('Weight'), - '#default_value' => $definition['weight'], + '#delta' => $delta, + '#default_value' => $link['weight'], '#description' => t('Menu links with lower weights are displayed before links with higher weights.'), ); } @@ -407,26 +548,18 @@ function menu_ui_form_node_form_alter(&$form, $form_state) { */ function menu_ui_node_submit(EntityInterface $node, $form, $form_state) { if (!empty($form_state['values']['menu'])) { - $definition = $form_state['values']['menu']; - if (empty($definition['enabled'])) { - if ($definition['entity_id']) { - $entity = entity_load('menu_link_content', $definition['entity_id']); - $entity->delete(); - } + $original_menu_id = !empty($node->menu) ? $node->menu->id() : NULL; + $node->menu = entity_create('menu_link', $form_state['values']['menu']); + // @todo Do not create a new entity in order to update it, see + // https://drupal.org/node/2241865 + // If this menu had a previous menu link associated, mark it as not new. + if ($original_menu_id) { + $node->menu->setOriginalId($original_menu_id); } - elseif (trim($definition['title'])) { - // Decompose the selected menu parent option into 'menu_name' and 'parent', - // if the form used the default parent selection widget. - if (!empty($definition['menu_parent'])) { - list($menu_name, $parent) = explode(':', $definition['menu_parent'], 2); - $definition['menu_name'] = $menu_name; - $definition['parent'] = $parent; - } - // @todo Figure out how to save this data without adding non-Field API - // properties to the node entity. https://www.drupal.org/node/2310173 - // We have to tack this onto the node so we can save it later when we - // have a node ID for any new node. - $node->menu = $definition; + // Decompose the selected menu parent option into 'menu_name' and 'plid', if + // the form used the default parent selection widget. + if (!empty($form_state['values']['menu']['parent'])) { + list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']); } } } @@ -440,8 +573,6 @@ function menu_ui_node_submit(EntityInterface $node, $form, $form_state) { * @see menu_ui_form_node_type_form_submit(). */ function menu_ui_form_node_type_form_alter(&$form, $form_state) { - /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */ - $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); $menu_options = menu_ui_get_menus(); $type = $form_state['controller']->getEntity(); if ($type->id()) { @@ -450,7 +581,7 @@ function menu_ui_form_node_type_form_alter(&$form, $form_state) { else { $config_values = array( 'available_menus' => array('main'), - 'parent' => 'main:', + 'parent' => 'main:0', ); } $form['menu'] = array( @@ -468,12 +599,12 @@ function menu_ui_form_node_type_form_alter(&$form, $form_state) { '#options' => $menu_options, '#description' => t('The menus available to place links in for this content type.'), ); - // @todo See if we can avoid pre-loading all options by changing the form or - // using a #process callback. https://www.drupal.org/node/2310319 - // To avoid an 'illegal option' error after saving the form we have to load - // all available menu parents. Otherwise, it is not possible to dynamically - // add options to the list using ajax. - $options = $menu_parent_selector->getParentSelectOptions(''); + // To avoid an 'illegal option' error after saving the form we have to load + // all available menu items. + // Otherwise it is not possible to dynamically add options to the list. + // @todo Convert menu_ui_parent_options() into a #process callback. + $menu_link = entity_create('menu_link', array('mlid' => 0)); + $options = menu_ui_parent_options(menu_ui_get_menus(), $menu_link); $form['menu']['menu_parent'] = array( '#type' => 'select', '#title' => t('Default parent item'), @@ -505,8 +636,7 @@ function menu_ui_form_node_type_form_submit(&$form, $form_state) { * @param $all * If FALSE return only user-added menus, or if TRUE also include * the menus defined by the system. - * - * @return array + * @return * An array with the machine-readable names as the keys, and human-readable * titles as the values. */ @@ -531,19 +661,3 @@ function menu_ui_preprocess_block(&$variables) { $variables['attributes']['role'] = 'navigation'; } } - - -/** - * Implements hook_system_breadcrumb_alter(). - */ -function menu_ui_system_breadcrumb_alter(array &$breadcrumb, RouteMatchInterface $route_match, array $context) { - // Custom breadcrumb behavior for editing menu links, we append a link to - // the menu in which the link is found. - if (($route_match->getRouteName() == 'menu_ui.link_edit') && $menu_link = $route_match->getParameter('menu_link_plugin')) { - if (($menu_link instanceof MenuLinkInterface)) { - // Add a link to the menu admin screen. - $menu = entity_load('menu', $menu_link->getMenuName()); - $breadcrumb[] = \Drupal::l($menu->label(), 'menu_ui.menu_edit', array('menu' => $menu->id())); - } - } -} diff --git a/core/modules/menu_ui/menu_ui.routing.yml b/core/modules/menu_ui/menu_ui.routing.yml index b095c344beaf..4be3bbf68076 100644 --- a/core/modules/menu_ui/menu_ui.routing.yml +++ b/core/modules/menu_ui/menu_ui.routing.yml @@ -21,30 +21,37 @@ menu_ui.parent_options_js: requirements: _permission: 'administer menu' +menu_ui.link_add: + path: '/admin/structure/menu/manage/{menu}/add' + defaults: + _content: '\Drupal\menu_ui\Controller\MenuController::addLink' + _title: 'Add menu link' + requirements: + _entity_create_access: 'menu_link' + menu_ui.link_edit: - path: '/admin/structure/menu/link/{menu_link_plugin}/edit' + path: '/admin/structure/menu/item/{menu_link}/edit' defaults: - _form: 'Drupal\menu_ui\Form\MenuLinkEditForm' + _entity_form: 'menu_link' _title: 'Edit menu link' - options: - parameters: - menu_link_plugin: - type: menu_link_plugin requirements: - _permission: 'administer menu' + _entity_access: 'menu_link.update' menu_ui.link_reset: - path: '/admin/structure/menu/link/{menu_link_plugin}/reset' + path: '/admin/structure/menu/item/{menu_link}/reset' defaults: - _form: 'Drupal\menu_ui\Form\MenuLinkResetForm' + _entity_form: 'menu_link.reset' _title: 'Reset menu link' - options: - parameters: - menu_link_plugin: - type: menu_link_plugin requirements: - _permission: 'administer menu' - _custom_access: '\Drupal\menu_ui\Form\MenuLinkResetForm::linkIsResettable' + _entity_access: 'menu_link.reset' + +menu_ui.link_delete: + path: '/admin/structure/menu/item/{menu_link}/delete' + defaults: + _entity_form: 'menu_link.delete' + _title: 'Delete menu link' + requirements: + _entity_access: 'menu_link.delete' menu_ui.menu_add: path: '/admin/structure/menu/add' diff --git a/core/modules/menu_ui/src/Controller/MenuController.php b/core/modules/menu_ui/src/Controller/MenuController.php index 95788bdc4fe5..fad5de5ea38c 100644 --- a/core/modules/menu_ui/src/Controller/MenuController.php +++ b/core/modules/menu_ui/src/Controller/MenuController.php @@ -9,9 +9,7 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Menu\MenuParentFormSelectorInterface; use Drupal\system\MenuInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -20,30 +18,6 @@ */ class MenuController extends ControllerBase { - /** - * The menu parent form service. - * - * @var \Drupal\Core\Menu\MenuParentFormSelectorInterface - */ - protected $menuParentSelector; - - /** - * Creates a new MenuController object. - * - * @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_form - * The menu parent form service. - */ - public function __construct(MenuParentFormSelectorInterface $menu_parent_form) { - $this->menuParentSelector = $menu_parent_form; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static($container->get('menu.parent_form_selector')); - } - /** * Gets all the available menus and menu items as a JavaScript array. * @@ -60,11 +34,29 @@ public function getParentOptions(Request $request) { $available_menus[$menu] = $menu; } } - $options = $this->menuParentSelector->getParentSelectOptions('', $available_menus); + $options = _menu_ui_get_options(menu_ui_get_menus(), $available_menus, array('mlid' => 0)); return new JsonResponse($options); } + /** + * Provides the menu link submission form. + * + * @param \Drupal\system\MenuInterface $menu + * An entity representing a custom menu. + * + * @return array + * Returns the menu link submission form. + */ + public function addLink(MenuInterface $menu) { + $menu_link = $this->entityManager()->getStorage('menu_link')->create(array( + 'mlid' => 0, + 'plid' => 0, + 'menu_name' => $menu->id(), + )); + return $this->entityFormBuilder()->getForm($menu_link); + } + /** * Route title callback. * diff --git a/core/modules/menu_ui/src/Form/MenuDeleteForm.php b/core/modules/menu_ui/src/Form/MenuDeleteForm.php index 5c3cd9619111..13cbf943a078 100644 --- a/core/modules/menu_ui/src/Form/MenuDeleteForm.php +++ b/core/modules/menu_ui/src/Form/MenuDeleteForm.php @@ -9,7 +9,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityConfirmFormBase; -use Drupal\Core\Menu\MenuLinkManagerInterface; +use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -19,11 +19,11 @@ class MenuDeleteForm extends EntityConfirmFormBase { /** - * The menu link manager. + * The menu link storage. * - * @var \Drupal\Core\Menu\MenuLinkManagerInterface + * @var \Drupal\Core\Entity\EntityStorageInterface */ - protected $menuLinkManager; + protected $storage; /** * The database connection. @@ -35,13 +35,13 @@ class MenuDeleteForm extends EntityConfirmFormBase { /** * Constructs a new MenuDeleteForm. * - * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager - * The menu link manager. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The menu link storage. * @param \Drupal\Core\Database\Connection $connection * The database connection. */ - public function __construct(MenuLinkManagerInterface $menu_link_manager, Connection $connection) { - $this->menuLinkManager = $menu_link_manager; + public function __construct(EntityStorageInterface $storage, Connection $connection) { + $this->storage = $storage; $this->connection = $connection; } @@ -50,7 +50,7 @@ public function __construct(MenuLinkManagerInterface $menu_link_manager, Connect */ public static function create(ContainerInterface $container) { return new static( - $container->get('plugin.manager.menu.link'), + $container->get('entity.manager')->getStorage('menu_link'), $container->get('database') ); } @@ -74,7 +74,7 @@ public function getCancelUrl() { */ public function getDescription() { $caption = ''; - $num_links = $this->menuLinkManager->countMenuLinks($this->entity->id()); + $num_links = $this->storage->countMenuLinks($this->entity->id()); if ($num_links) { $caption .= '<p>' . format_plural($num_links, '<strong>Warning:</strong> There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', '<strong>Warning:</strong> There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $this->entity->label())) . '</p>'; } @@ -100,17 +100,22 @@ public function submit(array $form, array &$form_state) { return; } - // Delete all links to the overview page for this menu. - // @todo Add a more generic helper function to the menu link plugin - // manager to remove links to a entity or other ID used as a route - // parameter that is being removed. Also, consider moving this to - // menu_ui.module as part of a generic response to entity deletion. - // https://www.drupal.org/node/2310329 - $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_ui.menu_edit', array('menu' => $this->entity->id()), TRUE); - foreach ($menu_links as $id => $link) { - $this->menuLinkManager->removeDefinition($id); + // Reset all the menu links defined by the menu_link.static service. + $result = \Drupal::entityQuery('menu_link') + ->condition('menu_name', $this->entity->id()) + ->condition('module', '', '>') + ->condition('machine_name', '', '>') + ->sort('depth', 'ASC') + ->execute(); + $menu_links = $this->storage->loadMultiple($result); + foreach ($menu_links as $link) { + $link->reset(); } + // Delete all links to the overview page for this menu. + $menu_links = $this->storage->loadByProperties(array('link_path' => 'admin/structure/menu/manage/' . $this->entity->id())); + menu_link_delete_multiple(array_keys($menu_links)); + // Delete the custom menu and all its menu links. $this->entity->delete(); diff --git a/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php b/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php new file mode 100644 index 000000000000..0a971d960772 --- /dev/null +++ b/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php @@ -0,0 +1,45 @@ +<?php + +/** + * @file + * Contains \Drupal\menu_ui\Form\MenuLinkDeleteForm. + */ + +namespace Drupal\menu_ui\Form; + +use Drupal\Core\Entity\EntityConfirmFormBase; +use Drupal\Core\Url; + +/** + * Defines a confirmation form for deletion of a single menu link. + */ +class MenuLinkDeleteForm extends EntityConfirmFormBase { + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to delete the custom menu link %item?', array('%item' => $this->entity->link_title)); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('menu_ui.menu_edit', array( + 'menu' => $this->entity->menu_name, + )); + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + menu_link_delete($this->entity->id()); + $t_args = array('%title' => $this->entity->link_title); + drupal_set_message(t('The menu link %title has been deleted.', $t_args)); + watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE); + $form_state['redirect_route'] = $this->getCancelUrl(); + } + +} diff --git a/core/modules/menu_ui/src/Form/MenuLinkEditForm.php b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php index cbe096c05e72..4e87ffbf2a82 100644 --- a/core/modules/menu_ui/src/Form/MenuLinkEditForm.php +++ b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php @@ -22,7 +22,7 @@ class MenuLinkEditForm extends FormBase { /** - * The class resolver. + * The class resolver * * @var \Drupal\Core\DependencyInjection\ClassResolverInterface */ diff --git a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php index 6bb22e1218b9..b5663aa58014 100644 --- a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php +++ b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php @@ -7,63 +7,19 @@ namespace Drupal\menu_ui\Form; +use Drupal\Core\Entity\EntityConfirmFormBase; use Drupal\Core\Url; -use Drupal\Core\Form\ConfirmFormBase; -use Drupal\Core\Menu\MenuLinkManagerInterface; -use Drupal\Core\Menu\MenuLinkInterface; -use Drupal\Core\Routing\Access\AccessInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines a confirmation form for resetting a single modified menu link. */ -class MenuLinkResetForm extends ConfirmFormBase { - - /** - * The menu link manager. - * - * @var \Drupal\Core\Menu\MenuLinkManagerInterface - */ - protected $menuLinkManager; - - /** - * The menu link. - * - * @var \Drupal\Core\Menu\MenuLinkInterface - */ - protected $link; - - /** - * Constructs a MenuLinkResetForm object. - * - * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager - * The menu link manager. - */ - public function __construct(MenuLinkManagerInterface $menu_link_manager) { - $this->menuLinkManager = $menu_link_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('plugin.manager.menu.link') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'menu_link_reset_confirm'; - } +class MenuLinkResetForm extends EntityConfirmFormBase { /** * {@inheritdoc} */ public function getQuestion() { - return $this->t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->link->getTitle())); + return t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->entity->link_title)); } /** @@ -71,7 +27,7 @@ public function getQuestion() { */ public function getCancelUrl() { return new Url('menu_ui.menu_edit', array( - 'menu' => $this->link->getMenuName(), + 'menu' => $this->entity->menu_name, )); } @@ -79,47 +35,23 @@ public function getCancelUrl() { * {@inheritdoc} */ public function getDescription() { - return $this->t('Any customizations will be lost. This action cannot be undone.'); + return t('Any customizations will be lost. This action cannot be undone.'); } /** * {@inheritdoc} */ public function getConfirmText() { - return $this->t('Reset'); + return t('Reset'); } /** * {@inheritdoc} */ - public function buildForm(array $form, array &$form_state, MenuLinkInterface $menu_link_plugin = NULL) { - $this->link = $menu_link_plugin; - - $form = parent::buildForm($form, $form_state); - return $form; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, array &$form_state) { - $this->link = $this->menuLinkManager->resetLink($this->link->getPluginId()); - drupal_set_message($this->t('The menu link was reset to its default settings.')); + public function submit(array $form, array &$form_state) { + $this->entity = $this->entity->reset(); + drupal_set_message(t('The menu link was reset to its default settings.')); $form_state['redirect_route'] = $this->getCancelUrl(); } - /** - * Checks access based on whether the link can be reset. - * - * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin - * The menu link plugin being checked. - * - * @return string - * AccessInterface::ALLOW when access was granted, otherwise - * AccessInterface::DENY. - */ - public function linkIsResettable(MenuLinkInterface $menu_link_plugin) { - return $menu_link_plugin->isResettable() ? AccessInterface::ALLOW : AccessInterface::DENY; - } - } diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php index 5e32eafce309..d854f3c0e44b 100644 --- a/core/modules/menu_ui/src/MenuForm.php +++ b/core/modules/menu_ui/src/MenuForm.php @@ -11,13 +11,9 @@ use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Menu\MenuLinkManagerInterface; -use Drupal\Core\Menu\MenuLinkTreeElement; -use Drupal\Core\Menu\MenuLinkTreeInterface; -use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Render\Element; -use Drupal\Core\Routing\UrlGeneratorTrait; -use Drupal\Core\Utility\LinkGeneratorInterface; +use Drupal\menu_link\MenuLinkStorageInterface; +use Drupal\menu_link\MenuTreeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -33,26 +29,19 @@ class MenuForm extends EntityForm { protected $entityQueryFactory; /** - * The menu link manager. + * The menu link storage. * - * @var \Drupal\Core\Menu\MenuLinkManagerInterface + * @var \Drupal\menu_link\MenuLinkStorageInterface */ - protected $menuLinkManager; + protected $menuLinkStorage; /** * The menu tree service. * - * @var \Drupal\Core\Menu\MenuLinkTreeInterface + * @var \Drupal\menu_link\MenuTreeInterface */ protected $menuTree; - /** - * The link generator. - * - * @var \Drupal\Core\Utility\LinkGeneratorInterface - */ - protected $linkGenerator; - /** * The overview tree form. * @@ -65,18 +54,15 @@ class MenuForm extends EntityForm { * * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory * The factory for entity queries. - * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager - * The menu link manager. - * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree + * @param \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage + * The menu link storage. + * @param \Drupal\menu_link\MenuTreeInterface $menu_tree * The menu tree service. - * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator - * The link generator. */ - public function __construct(QueryFactory $entity_query_factory, MenuLinkManagerInterface $menu_link_manager, MenuLinkTreeInterface $menu_tree, LinkGeneratorInterface $link_generator) { + public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageInterface $menu_link_storage, MenuTreeInterface $menu_tree) { $this->entityQueryFactory = $entity_query_factory; - $this->menuLinkManager = $menu_link_manager; + $this->menuLinkStorage = $menu_link_storage; $this->menuTree = $menu_tree; - $this->linkGenerator = $link_generator; } /** @@ -85,9 +71,8 @@ public function __construct(QueryFactory $entity_query_factory, MenuLinkManagerI public static function create(ContainerInterface $container) { return new static( $container->get('entity.query'), - $container->get('plugin.manager.menu.link'), - $container->get('menu.link_tree'), - $container->get('link_generator') + $container->get('entity.manager')->getStorage('menu_link'), + $container->get('menu_link.tree') ); } @@ -103,16 +88,16 @@ public function form(array $form, array &$form_state) { $form['label'] = array( '#type' => 'textfield', - '#title' => $this->t('Title'), + '#title' => t('Title'), '#default_value' => $menu->label(), '#required' => TRUE, ); $form['id'] = array( '#type' => 'machine_name', - '#title' => $this->t('Menu name'), + '#title' => t('Menu name'), '#default_value' => $menu->id(), '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI, - '#description' => $this->t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'), + '#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'), '#machine_name' => array( 'exists' => array($this, 'menuNameExists'), 'source' => array('label'), @@ -135,11 +120,28 @@ public function form(array $form, array &$form_state) { '#languages' => LanguageInterface::STATE_ALL, '#default_value' => $menu->language()->getId(), ); + // Unlike the menu langcode, the default language configuration for menu + // links only works with language module installed. + if ($this->moduleHandler->moduleExists('language')) { + $form['default_menu_links_language'] = array( + '#type' => 'details', + '#title' => t('Menu links language'), + '#open' => TRUE, + ); + $form['default_menu_links_language']['default_language'] = array( + '#type' => 'language_configuration', + '#entity_information' => array( + 'entity_type' => 'menu_link', + 'bundle' => $menu->id(), + ), + '#default_value' => language_get_default_configuration('menu_link', $menu->id()), + ); + } // Add menu links administration form for existing menus. if (!$menu->isNew() || $menu->isLocked()) { // Form API supports constructing and validating self-contained sections - // within forms, but does not allow handling the form section's submission + // within forms, but does not allow to handle the form section's submission // equally separated yet. Therefore, we use a $form_state key to point to // the parents of the form section. // @see self::submitOverviewForm() @@ -167,7 +169,41 @@ public function menuNameExists($value) { } // Check for a link assigned to this menu. - return $this->menuLinkManager->menuNameInUse($value); + return $this->entityQueryFactory->get('menu_link')->condition('menu_name', $value)->range(0, 1)->count()->execute(); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, array &$form_state) { + $actions = parent::actions($form, $form_state); + + // Add the language configuration submit handler. This is needed because the + // submit button has custom submit handlers. + if ($this->moduleHandler->moduleExists('language')) { + array_unshift($actions['submit']['#submit'],'language_configuration_element_submit'); + array_unshift($actions['submit']['#submit'], array($this, 'languageConfigurationSubmit')); + } + // We cannot leverage the regular submit handler definition because we have + // button-specific ones here. Hence we need to explicitly set it for the + // submit action, otherwise it would be ignored. + if ($this->moduleHandler->moduleExists('content_translation')) { + array_unshift($actions['submit']['#submit'], 'content_translation_language_configuration_element_submit'); + } + return $actions; + } + + /** + * Submit handler to update the bundle for the default language configuration. + */ + public function languageConfigurationSubmit(array &$form, array &$form_state) { + // Since the machine name is not known yet, and it can be changed anytime, + // we have to also update the bundle property for the default language + // configuration in order to have the correct bundle value. + $form_state['language']['default_language']['bundle'] = $form_state['values']['id']; + // Clear cache so new menus (bundles) show on the language settings admin + // page. + \Drupal::entityManager()->clearCachedBundles(); } /** @@ -181,13 +217,13 @@ public function save(array $form, array &$form_state) { $status = $menu->save(); - $edit_link = $this->linkGenerator->generateFromUrl($this->t('Edit'), $this->entity->urlInfo()); + $edit_link = \Drupal::linkGenerator()->generateFromUrl($this->t('Edit'), $this->entity->urlInfo()); if ($status == SAVED_UPDATED) { - drupal_set_message($this->t('Menu %label has been updated.', array('%label' => $menu->label()))); + drupal_set_message(t('Menu %label has been updated.', array('%label' => $menu->label()))); watchdog('menu', 'Menu %label has been updated.', array('%label' => $menu->label()), WATCHDOG_NOTICE, $edit_link); } else { - drupal_set_message($this->t('Menu %label has been added.', array('%label' => $menu->label()))); + drupal_set_message(t('Menu %label has been added.', array('%label' => $menu->label()))); watchdog('menu', 'Menu %label has been added.', array('%label' => $menu->label()), WATCHDOG_NOTICE, $edit_link); } @@ -217,30 +253,26 @@ protected function buildOverviewForm(array &$form, array &$form_state) { $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/css/menu.admin.css'); - $tree = $this->menuTree->load($this->entity->id(), new MenuTreeParameters()); + $links = array(); + $query = $this->entityQueryFactory->get('menu_link') + ->condition('menu_name', $this->entity->id()); + for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { + $query->sort('p' . $i, 'ASC'); + } + $result = $query->execute(); + if (!empty($result)) { + $links = $this->menuLinkStorage->loadMultiple($result); + } + + $delta = max(count($links), 50); // We indicate that a menu administrator is running the menu access check. $this->getRequest()->attributes->set('_menu_admin', TRUE); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - ); - $tree = $this->menuTree->transform($tree, $manipulators); + $tree = $this->menuTree->buildTreeData($links); $this->getRequest()->attributes->set('_menu_admin', FALSE); - // Determine the delta; the number of weights to be made available. - $count = function(array $tree) { - $sum = function ($carry, MenuLinkTreeElement $item) { - return $carry + $item->count(); - }; - return array_reduce($tree, $sum); - }; - $delta = max($count($tree), 50); - $form = array_merge($form, $this->buildOverviewTreeForm($tree, $delta)); - $destination = $this->getUrlGenerator()->getPathFromRoute('menu_ui.menu_edit', array('menu' => $this->entity->id())); - $url = $destination = $this->url('menu_link_content.link_add', array('menu' => $this->entity->id()), array('query' => array('destination' => $destination))); - $form['#empty_text'] = $this->t('There are no menu links yet. <a href="@url">Add link</a>.', array('@url' => $url)); + $form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/' . $this->entity->id() .'/add'))); return $form; } @@ -249,7 +281,7 @@ protected function buildOverviewForm(array &$form, array &$form_state) { * Recursive helper function for buildOverviewForm(). * * @param $tree - * The tree retrieved by \Drupal\Core\Menu\MenuLinkTreeInterface::load(). + * The menu_tree retrieved by menu_tree_data. * @param $delta * The default number of menu items used in the menu weight selector is 50. * @@ -258,87 +290,70 @@ protected function buildOverviewForm(array &$form, array &$form_state) { */ protected function buildOverviewTreeForm($tree, $delta) { $form = &$this->overviewTreeForm; - foreach ($tree as $element) { - /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ - $link = $element->link; - if ($link) { - $id = 'menu_plugin_id:' . $link->getPluginId(); - $form[$id]['#item'] = $element; - $form[$id]['#attributes'] = $link->isHidden() ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled')); - $form[$id]['title']['#markup'] = $this->linkGenerator->generateFromUrl($link->getTitle(), $link->getUrlObject(), $link->getOptions()); - if ($link->isHidden()) { - $form[$id]['title']['#markup'] .= ' (' . $this->t('disabled') . ')'; + foreach ($tree as $data) { + $item = $data['link']; + // Don't show callbacks; these have $item['hidden'] < 0. + if ($item && $item['hidden'] >= 0) { + $mlid = 'mlid:' . $item['mlid']; + $form[$mlid]['#item'] = $item; + $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled')); + $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['localized_options']); + if ($item['hidden']) { + $form[$mlid]['title']['#markup'] .= ' (' . t('disabled') . ')'; } - elseif (($url = $link->getUrlObject()) && !$url->isExternal() && $url->getRouteName() == 'user.page') { - $form[$id]['title']['#markup'] .= ' (' . $this->t('logged in users only') . ')'; + elseif ($item['link_path'] == 'user' && $item['module'] == 'user') { + $form[$mlid]['title']['#markup'] .= ' (' . t('logged in users only') . ')'; } - $form[$id]['enabled'] = array( + $form[$mlid]['hidden'] = array( '#type' => 'checkbox', - '#title' => $this->t('Enable @title menu link', array('@title' => $link->getTitle())), + '#title' => t('Enable @title menu link', array('@title' => $item['title'])), '#title_display' => 'invisible', - '#default_value' => !$link->isHidden(), + '#default_value' => !$item['hidden'], ); - $form[$id]['weight'] = array( + $form[$mlid]['weight'] = array( '#type' => 'weight', '#delta' => $delta, - '#default_value' => $link->getWeight(), - '#title' => $this->t('Weight for @title', array('@title' => $link->getTitle())), + '#default_value' => $item['weight'], + '#title' => t('Weight for @title', array('@title' => $item['title'])), '#title_display' => 'invisible', ); - $form[$id]['id'] = array( + $form[$mlid]['mlid'] = array( '#type' => 'hidden', - '#value' => $link->getPluginId(), + '#value' => $item['mlid'], ); - $form[$id]['parent'] = array( + $form[$mlid]['plid'] = array( '#type' => 'hidden', - '#default_value' => $link->getParent(), + '#default_value' => $item['plid'], ); // Build a list of operations. $operations = array(); $operations['edit'] = array( - 'title' => $this->t('Edit'), + 'title' => t('Edit'), + 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/edit', ); - // Allow for a custom edit link per plugin. - $edit_route = $link->getEditRoute(); - if ($edit_route) { - $operations['edit'] += $edit_route; - // Bring the user back to the menu overview. - $operations['edit']['query']['destination'] = $this->entity->url(); - } - else { - // Fall back to the standard edit link. - $operations['edit'] += array( - 'route_name' => 'menu_ui.link_edit', - 'route_parameters' => array('menu_link_plugin' => $link->getPluginId()), + // Only items created by the Menu UI module can be deleted. + if ($item->access('delete')) { + $operations['delete'] = array( + 'title' => t('Delete'), + 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/delete', ); } - // Links can either be reset or deleted, not both. - if ($link->isResettable()) { + // Set the reset column. + elseif ($item->access('reset')) { $operations['reset'] = array( - 'title' => $this->t('Reset'), - 'route_name' => 'menu_ui.link_reset', - 'route_parameters' => array('menu_link_plugin' => $link->getPluginId()), + 'title' => t('Reset'), + 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/reset', ); } - elseif ($delete_link = $link->getDeleteRoute()) { - $operations['delete'] = $delete_link; - $operations['delete']['query']['destination'] = $this->entity->url(); - $operations['delete']['title'] = $this->t('Delete'); - } - if ($link->isTranslatable()) { - $operations['translate'] = array( - 'title' => $this->t('Translate'), - ) + (array) $link->getTranslateRoute(); - } - $form[$id]['operations'] = array( + $form[$mlid]['operations'] = array( '#type' => 'operations', '#links' => $operations, ); } - if ($element->subtree) { - $this->buildOverviewTreeForm($element->subtree, $delta); + if ($data['below']) { + $this->buildOverviewTreeForm($data['below'], $delta); } } return $form; @@ -348,7 +363,7 @@ protected function buildOverviewTreeForm($tree, $delta) { * Submit handler for the menu overview form. * * This function takes great care in saving parent items first, then items - * underneath them. Saving items in the incorrect order can break the tree. + * underneath them. Saving items in the incorrect order can break the menu tree. */ protected function submitOverviewForm(array $complete_form, array &$form_state) { // Form API supports constructing and validating self-contained sections @@ -369,30 +384,32 @@ protected function submitOverviewForm(array $complete_form, array &$form_state) // Update our original form with the new order. $form = array_intersect_key(array_merge($order, $form), $form); - $fields = array('weight', 'parent', 'enabled'); - foreach (Element::children($form) as $id) { - if (isset($form[$id]['#item'])) { - $element = $form[$id]; - $updated_values = array(); + $updated_items = array(); + $fields = array('weight', 'plid'); + foreach (Element::children($form) as $mlid) { + if (isset($form[$mlid]['#item'])) { + $element = $form[$mlid]; // Update any fields that have changed in this menu item. foreach ($fields as $field) { if ($element[$field]['#value'] != $element[$field]['#default_value']) { - // Hidden is a special case, the form value needs to be reversed. - if ($field == 'enabled') { - $updated_values['hidden'] = $element['enabled']['#value'] ? 0 : 1; - } - else { - $updated_values[$field] = $element[$field]['#value']; - } + $element['#item'][$field] = $element[$field]['#value']; + $updated_items[$mlid] = $element['#item']; } } - if ($updated_values) { - // Use the ID from the actual plugin instance since the hidden value - // in the form could be tampered with. - $this->menuLinkManager->updateDefinition($element['#item']->link->getPLuginId(), $updated_values); + // Hidden is a special case, the value needs to be reversed. + if ($element['hidden']['#value'] != $element['hidden']['#default_value']) { + // Convert to integer rather than boolean due to PDO cast to string. + $element['#item']['hidden'] = $element['hidden']['#value'] ? 0 : 1; + $updated_items[$mlid] = $element['#item']; } } } + + // Save all our changed items to the database. + foreach ($updated_items as $item) { + $item['customized'] = 1; + $item->save(); + } } } diff --git a/core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php b/core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php deleted file mode 100644 index 9fee161129eb..000000000000 --- a/core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\menu_ui\Plugin\Menu\LocalAction\MenuLinkAdd. - */ - -namespace Drupal\menu_ui\Plugin\Menu\LocalAction; - -use Drupal\Core\Menu\LocalActionDefault; -use Symfony\Cmf\Component\Routing\RouteObjectInterface; -use Symfony\Component\HttpFoundation\Request; - -/** - * Modifies the 'Add link' local action to add a destination. - */ -class MenuLinkAdd extends LocalActionDefault { - - /** - * {@inheritdoc} - */ - public function getOptions(Request $request) { - $options = parent::getOptions($request); - // Append the current path as destination to the query string. - if ($request->attributes->has(RouteObjectInterface::ROUTE_NAME)) { - $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME); - $raw_variables = array(); - if ($request->attributes->has('_raw_variables')) { - $raw_variables = $request->attributes->get('_raw_variables')->all(); - } - // @todo Use RouteMatch instead of Request. - // https://www.drupal.org/node/2294157 - $options['query']['destination'] = \Drupal::urlGenerator()->generateFromRoute($route_name, $raw_variables); - } - return $options; - } - -} diff --git a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php index 4a191826e27a..c863b8d8cceb 100644 --- a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php +++ b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\menu_ui\Tests\MenuCacheTagsTest. + * Contains \Drupal\menu\Tests\MenuCacheTagsTest. */ namespace Drupal\menu_ui\Tests; @@ -37,10 +37,12 @@ public function testMenuBlock() { 'description' => 'Description text', )); $menu->save(); - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - // Move a link into the new menu. - $menu_link = $menu_link_manager->updateDefinition('test_page_test.test_page', array('menu_name' => 'llama', 'parent' => '')); + $menu_link = entity_create('menu_link', array( + 'link_path' => '<front>', + 'link_title' => 'Vicuña', + 'menu_name' => 'llama', + )); + $menu_link->save(); $block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'provider' => 'system', 'region' => 'footer')); // Prime the page cache. @@ -58,6 +60,7 @@ public function testMenuBlock() { ); $this->verifyPageCache($path, 'HIT', $expected_tags); + // Verify that after modifying the menu, there is a cache miss. $this->pass('Test modification of menu.', 'Debug'); $menu->label = 'Awesome llama'; @@ -67,23 +70,23 @@ public function testMenuBlock() { // Verify a cache hit. $this->verifyPageCache($path, 'HIT'); - // Verify that after modifying the menu link weight, there is a cache miss. - $menu_link_manager->updateDefinition('test_page_test.test_page', array('weight' => -10)); + + // Verify that after modifying the menu link, there is a cache miss. $this->pass('Test modification of menu link.', 'Debug'); + $menu_link->link_title = 'Guanaco'; + $menu_link->save(); $this->verifyPageCache($path, 'MISS'); // Verify a cache hit. $this->verifyPageCache($path, 'HIT'); + // Verify that after adding a menu link, there is a cache miss. $this->pass('Test addition of menu link.', 'Debug'); - $menu_link_2 = entity_create('menu_link_content', array( - 'id' => '', - 'parent' => '', - 'title' => 'Alpaca', + $menu_link_2 = entity_create('menu_link', array( + 'link_path' => '<front>', + 'link_title' => 'Alpaca', 'menu_name' => 'llama', - 'route_name' => '<front>', - 'bundle' => 'menu_name', )); $menu_link_2->save(); $this->verifyPageCache($path, 'MISS'); @@ -91,15 +94,16 @@ public function testMenuBlock() { // Verify a cache hit. $this->verifyPageCache($path, 'HIT'); - // Verify that after resetting the first menu link, there is a cache miss. - $this->pass('Test reset of menu link.', 'Debug'); - $this->assertTrue($menu_link->isResettable(), 'First link can be reset'); - $menu_link = $menu_link_manager->resetLink($menu_link->getPluginId()); + + // Verify that after deleting the first menu link, there is a cache miss. + $this->pass('Test deletion of menu link.', 'Debug'); + $menu_link->delete(); $this->verifyPageCache($path, 'MISS'); // Verify a cache hit. $this->verifyPageCache($path, 'HIT', $expected_tags); + // Verify that after deleting the menu, there is a cache miss. $this->pass('Test deletion of menu.', 'Debug'); $menu->delete(); diff --git a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php index 88d9150fa58d..9267c2847240 100644 --- a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php +++ b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php @@ -59,12 +59,21 @@ function testMenuLanguage() { 'description' => '', 'label' => $label, 'langcode' => 'aa', + 'default_language[langcode]' => 'bb', + 'default_language[language_show]' => TRUE, ); $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save')); - language_save_default_configuration('menu_link_content', 'menu_link_content', array('langcode' => 'bb', 'language_show' => TRUE)); - // Check menu language. + // Check that the language settings were saved. + $this->assertEqual(entity_load('menu', $menu_name)->language()->getId(), $edit['langcode']); + $language_settings = language_get_default_configuration('menu_link', $menu_name); + $this->assertEqual($language_settings['langcode'], 'bb'); + $this->assertEqual($language_settings['language_show'], TRUE); + + // Check menu language and item language configuration. $this->assertOptionSelected('edit-langcode', $edit['langcode'], 'The menu language was correctly selected.'); + $this->assertOptionSelected('edit-default-language-langcode', $edit['default_language[langcode]'], 'The menu link default language was correctly selected.'); + $this->assertFieldChecked('edit-default-language-language-show'); // Test menu link language. $link_path = '<front>'; @@ -72,35 +81,41 @@ function testMenuLanguage() { // Add a menu link. $link_title = $this->randomString(); $edit = array( - 'title[0][value]' => $link_title, - 'url' => $link_path, + 'link_title' => $link_title, + 'link_path' => $link_path, ); $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save')); // Check the link was added with the correct menu link default language. - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title)); + $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $link_title)); $menu_link = reset($menu_links); - $this->assertMenuLink($menu_link->getPluginId(), array( + $this->assertMenuLink($menu_link->id(), array( 'menu_name' => $menu_name, - 'route_name' => '<front>', + 'link_path' => $link_path, 'langcode' => 'bb', )); // Edit menu link default, changing it to cc. - language_save_default_configuration('menu_link_content', 'menu_link_content', array('langcode' => 'cc', 'language_show' => TRUE)); + $edit = array( + 'default_language[langcode]' => 'cc', + ); + $this->drupalPostForm("admin/structure/menu/manage/$menu_name", $edit, t('Save')); + + // Check cc is the menu link default. + $this->assertOptionSelected('edit-default-language-langcode', $edit['default_language[langcode]'], 'The menu link default language was correctly selected.'); // Add a menu link. $link_title = $this->randomString(); $edit = array( - 'title[0][value]' => $link_title, - 'url' => $link_path, + 'link_title' => $link_title, + 'link_path' => $link_path, ); $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save')); // Check the link was added with the correct new menu link default language. - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title)); + $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $link_title)); $menu_link = reset($menu_links); - $this->assertMenuLink($menu_link->getPluginId(), array( + $this->assertMenuLink($menu_link->id(), array( 'menu_name' => $menu_name, - 'route_name' => '<front>', + 'link_path' => $link_path, 'langcode' => 'cc', )); @@ -109,9 +124,9 @@ function testMenuLanguage() { 'langcode' => 'bb', ); $this->drupalPostForm('admin/structure/menu/item/' . $menu_link->id() . '/edit', $edit, t('Save')); - $this->assertMenuLink($menu_link->getPluginId(), array( + $this->assertMenuLink($menu_link->id(), array( 'menu_name' => $menu_name, - 'route_name' => '<front>', + 'link_path' => $link_path, 'langcode' => 'bb', )); @@ -123,7 +138,16 @@ function testMenuLanguage() { $this->assertOptionSelected('edit-langcode', 'bb', 'The menu link language was correctly selected.'); // Edit menu to hide the language select on menu link item add. - language_save_default_configuration('menu_link_content', 'menu_link_content', array('langcode' => 'cc', 'language_show' => FALSE)); + $edit = array( + 'default_language[language_show]' => FALSE, + ); + $this->drupalPostForm("admin/structure/menu/manage/$menu_name", $edit, t('Save')); + $this->assertNoFieldChecked('edit-default-language-language-show'); + + // Check that the language settings were saved. + $language_settings = language_get_default_configuration('menu_link', $menu_name); + $this->assertEqual($language_settings['langcode'], 'cc'); + $this->assertEqual($language_settings['language_show'], FALSE); // Check that the language selector is not available on menu link add page. $this->drupalGet("admin/structure/menu/manage/$menu_name/add"); diff --git a/core/modules/menu_ui/src/Tests/MenuNodeTest.php b/core/modules/menu_ui/src/Tests/MenuNodeTest.php index 7251397bdd91..819bd5632b73 100644 --- a/core/modules/menu_ui/src/Tests/MenuNodeTest.php +++ b/core/modules/menu_ui/src/Tests/MenuNodeTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\menu_ui\Tests\MenuNodeTest. + * Definition of Drupal\menu_ui\Tests\MenuNodeTest. */ namespace Drupal\menu_ui\Tests; @@ -65,7 +65,7 @@ function testMenuNodeFormWidget() { $edit = array( 'menu_options[main]' => 1, 'menu_options[tools]' => 1, - 'menu_parent' => 'main:', + 'menu_parent' => 'main:0', ); $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type')); @@ -93,7 +93,7 @@ function testMenuNodeFormWidget() { // Edit the node and create a menu link. $edit = array( 'menu[enabled]' => 1, - 'menu[title]' => $node_title, + 'menu[link_title]' => $node_title, 'menu[weight]' => 17, ); $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); @@ -102,7 +102,7 @@ function testMenuNodeFormWidget() { $this->assertLink($node_title); $this->drupalGet('node/' . $node->id() . '/edit'); - $this->assertFieldById('edit-menu-weight', 17, 'Menu weight correct in edit form'); + $this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form'); // Edit the node and remove the menu link. $edit = array( @@ -114,12 +114,10 @@ function testMenuNodeFormWidget() { $this->assertNoLink($node_title); // Add a menu link to the Administration menu. - $item = entity_create('menu_link_content', array( - 'route_name' => 'node.view', - 'route_parameters' => array('node' => $node->id()), - 'title' => $this->randomName(16), + $item = entity_create('menu_link', array( + 'link_path' => 'node/' . $node->id(), + 'link_title' => $this->randomName(16), 'menu_name' => 'admin', - 'bundle' => 'menu_link_content', )); $item->save(); @@ -129,30 +127,27 @@ function testMenuNodeFormWidget() { $this->assertText('Provide a menu link', 'Link in not allowed menu not shown in node edit form'); // Assert that the link is still in the Administration menu after save. $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - $link = entity_load('menu_link_content', $item->id()); + $link = menu_link_load($item['mlid']); $this->assertTrue($link, 'Link in not allowed menu still exists after saving node'); // Move the menu link back to the Tools menu. - $item->menu_name->value = 'tools'; - $item->save(); + $item['menu_name'] = 'tools'; + menu_link_save($item); // Create a second node. $child_node = $this->drupalCreateNode(array('type' => 'article')); // Assign a menu link to the second node, being a child of the first one. - $child_item = entity_create('menu_link_content', array( - 'route_name' => 'node.view', - 'route_parameters' => array('node' => $child_node->id()), - 'title' => $this->randomName(16), - 'parent' => $item->getPluginId(), - 'menu_name' => $item->getMenuName(), - 'bundle' => 'menu_link_content', + $child_item = entity_create('menu_link', array( + 'link_path' => 'node/'. $child_node->id(), + 'link_title' => $this->randomName(16), + 'plid' => $item['mlid'], )); $child_item->save(); // Edit the first node. $this->drupalGet('node/'. $node->id() .'/edit'); // Assert that it is not possible to set the parent of the first node to itself or the second node. - $this->assertNoOption('edit-menu-menu-parent', 'tools:'. $item->getPluginId()); - $this->assertNoOption('edit-menu-menu-parent', 'tools:'. $child_item->getPluginId()); + $this->assertNoOption('edit-menu-parent', 'tools:'. $item['mlid']); + $this->assertNoOption('edit-menu-parent', 'tools:'. $child_item['mlid']); // Assert that unallowed Administration menu is not available in options. - $this->assertNoOption('edit-menu-menu-parent', 'admin:'); + $this->assertNoOption('edit-menu-parent', 'admin:0'); } } diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php index ce7fd57b7096..f707eb9cf1e2 100644 --- a/core/modules/menu_ui/src/Tests/MenuTest.php +++ b/core/modules/menu_ui/src/Tests/MenuTest.php @@ -2,14 +2,12 @@ /** * @file - * Contains \Drupal\menu_ui\Tests\MenuTest. + * Definition of Drupal\menu_ui\Tests\MenuTest. */ namespace Drupal\menu_ui\Tests; use Drupal\Component\Serialization\Json; -use Drupal\Core\Menu\MenuLinkInterface; -use Drupal\menu_link_content\Entity\MenuLinkContent; use Drupal\system\Entity\Menu; /** @@ -51,7 +49,7 @@ class MenuTest extends MenuWebTestBase { /** * An array of test menu links. * - * @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface[] + * @var array */ protected $items; @@ -80,20 +78,17 @@ function testMenu() { // Verify that the menu links rebuild is idempotent and leaves the same // number of links in the table. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $before_count = $menu_link_manager->countMenuLinks(NULL); - $menu_link_manager->rebuild(); - $after_count = $menu_link_manager->countMenuLinks(NULL); - $this->assertIdentical($before_count, $after_count, 'MenuLinkManager::rebuild() does not add more links'); + $before_count = db_query('SELECT COUNT(*) FROM {menu_links}')->fetchField(); + menu_link_rebuild_defaults(); + $after_count = db_query('SELECT COUNT(*) FROM {menu_links}')->fetchField(); + $this->assertIdentical($before_count, $after_count, 'menu_link_rebuild_defaults() does not add more links'); // Do standard user tests. // Login the user. $this->drupalLogin($this->authenticated_user); $this->verifyAccess(403); - foreach ($this->items as $item) { // Paths were set as 'node/$nid'. - $node = node_load($item->getRouteParameters()['node']); + $node = node_load(substr($item['link_path'], 5)); $this->verifyMenuLink($item, $node); } @@ -109,20 +104,20 @@ function testMenu() { $this->deleteCustomMenu(); // Modify and reset a standard menu link. - $instance = $this->getStandardMenuLink(); - $old_weight = $instance->getWeight(); - // Edit the static menu link. - $edit = array(); - $edit['weight'] = 10; - $id = $instance->getPluginId(); - $this->drupalPostForm("admin/structure/menu/link/$id/edit", $edit, t('Save')); - $this->assertResponse(200); - $this->assertText('The menu link has been saved.'); - $menu_link_manager->resetDefinitions(); - - $instance = $menu_link_manager->createInstance($instance->getPluginId()); - $this->assertEqual($edit['weight'], $instance->getWeight(), 'Saving an existing link updates the weight.'); - $this->resetMenuLink($instance, $old_weight); + $item = $this->getStandardMenuLink(); + $old_title = $item['link_title']; + $this->modifyMenuLink($item); + $item = entity_load('menu_link', $item['mlid']); + // Verify that a change to the description is saved. + $description = $this->randomName(16); + $item['options']['attributes']['title'] = $description; + $return_value = menu_link_save($item); + // Save the menu link again to test the return value of the procedural save + // helper. + $this->assertIdentical($return_value, $item->save(), 'Return value of menu_link_save() is identical to the return value of $menu_link->save().'); + $saved_item = entity_load('menu_link', $item['mlid']); + $this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)'); + $this->resetMenuLink($item, $old_title); } /** @@ -219,7 +214,7 @@ function deleteCustomMenu() { $this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $label)), 'Custom menu was deleted'); $this->assertNull(Menu::load($menu_name), 'Custom menu was deleted'); // Test if all menu links associated to the menu were removed from database. - $result = entity_load_multiple_by_properties('menu_link_content', array('menu_name' => $menu_name)); + $result = entity_load_multiple_by_properties('menu_link', array('menu_name' => $menu_name)); $this->assertFalse($result, 'All menu links associated to the custom menu were deleted.'); // Make sure there's no delete button on system menus. @@ -250,32 +245,33 @@ function doMenuTests() { )); // Add menu links. - $item1 = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name, TRUE); - $item2 = $this->addMenuLink($item1->getPluginId(), 'node/' . $node2->id(), $menu_name, FALSE); - $item3 = $this->addMenuLink($item2->getPluginId(), 'node/' . $node3->id(), $menu_name); - - // Hierarchy - // <$menu_name> - // - item1 - // -- item2 - // --- item3 - - $this->assertMenuLink($item1->getPluginId(), array( - 'children' => array($item2->getPluginId(), $item3->getPluginId()), - 'parents' => array($item1->getPluginId()), + $item1 = $this->addMenuLink(0, 'node/' . $node1->id(), $menu_name); + $item2 = $this->addMenuLink($item1['mlid'], 'node/' . $node2->id(), $menu_name, FALSE); + $item3 = $this->addMenuLink($item2['mlid'], 'node/' . $node3->id(), $menu_name); + $this->assertMenuLink($item1['mlid'], array( + 'depth' => 1, + 'has_children' => 1, + 'p1' => $item1['mlid'], + 'p2' => 0, // We assert the language code here to make sure that the language // selection element degrades gracefully without the Language module. 'langcode' => 'en', )); - $this->assertMenuLink($item2->getPluginId(), array( - 'children' => array($item3->getPluginId()), - 'parents' => array($item2->getPluginId(), $item1->getPluginId()), + $this->assertMenuLink($item2['mlid'], array( + 'depth' => 2, 'has_children' => 1, + 'p1' => $item1['mlid'], + 'p2' => $item2['mlid'], + 'p3' => 0, // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item3->getPluginId(), array( - 'children' => array(), - 'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item1->getPluginId()), + $this->assertMenuLink($item3['mlid'], array( + 'depth' => 3, + 'has_children' => 0, + 'p1' => $item1['mlid'], + 'p2' => $item2['mlid'], + 'p3' => $item3['mlid'], + 'p4' => 0, // See above. 'langcode' => 'en', )); @@ -286,37 +282,34 @@ function doMenuTests() { $this->verifyMenuLink($item3, $node3, $item2, $node2); // Add more menu links. - $item4 = $this->addMenuLink('', 'node/' . $node4->id(), $menu_name); - $item5 = $this->addMenuLink($item4->getPluginId(), 'node/' . $node5->id(), $menu_name); + $item4 = $this->addMenuLink(0, 'node/' . $node4->id(), $menu_name); + $item5 = $this->addMenuLink($item4['mlid'], 'node/' . $node5->id(), $menu_name); // Create a menu link pointing to an alias. - $item6 = $this->addMenuLink($item4->getPluginId(), 'node5', $menu_name, TRUE, '0'); - - // Hierarchy - // <$menu_name> - // - item1 - // -- item2 - // --- item3 - // - item4 - // -- item5 - // -- item6 - - $this->assertMenuLink($item4->getPluginId(), array( - 'children' => array($item5->getPluginId(), $item6->getPluginId()), - 'parents' => array($item4->getPluginId()), + $item6 = $this->addMenuLink($item4['mlid'], 'node5', $menu_name, TRUE, '0', 'node/' . $node5->id()); + $this->assertMenuLink($item4['mlid'], array( + 'depth' => 1, + 'has_children' => 1, + 'p1' => $item4['mlid'], + 'p2' => 0, // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item5->getPluginId(), array( - 'children' => array(), - 'parents' => array($item5->getPluginId(), $item4->getPluginId()), + $this->assertMenuLink($item5['mlid'], array( + 'depth' => 2, + 'has_children' => 0, + 'p1' => $item4['mlid'], + 'p2' => $item5['mlid'], + 'p3' => 0, + // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item6->getPluginId(), array( - 'children' => array(), - 'parents' => array($item6->getPluginId(), $item4->getPluginId()), - 'route_name' => 'node.view', - 'route_parameters' => array('node' => $node5->id()), - 'url' => '', + $this->assertMenuLink($item6['mlid'], array( + 'depth' => 2, + 'has_children' => 0, + 'p1' => $item4['mlid'], + 'p2' => $item6['mlid'], + 'p3' => 0, + 'link_path' => 'node/' . $node5->id(), // See above. 'langcode' => 'en', )); @@ -330,44 +323,50 @@ function doMenuTests() { $this->toggleMenuLink($item2); // Move link and verify that descendants are updated. - $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name); - // Hierarchy - // <$menu_name> - // - item1 - // - item4 - // -- item5 - // --- item2 - // ---- item3 - // -- item6 - - $this->assertMenuLink($item1->getPluginId(), array( - 'children' => array(), - 'parents' => array($item1->getPluginId()), + $this->moveMenuLink($item2, $item5['mlid'], $menu_name); + $this->assertMenuLink($item1['mlid'], array( + 'depth' => 1, + 'has_children' => 0, + 'p1' => $item1['mlid'], + 'p2' => 0, // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item4->getPluginId(), array( - 'children' => array($item5->getPluginId(), $item6->getPluginId(), $item2->getPluginId(), $item3->getPluginId()), - 'parents' => array($item4->getPluginId()), + $this->assertMenuLink($item4['mlid'], array( + 'depth' => 1, + 'has_children' => 1, + 'p1' => $item4['mlid'], + 'p2' => 0, // See above. 'langcode' => 'en', )); - - $this->assertMenuLink($item5->getPluginId(), array( - 'children' => array($item2->getPluginId(), $item3->getPluginId()), - 'parents' => array($item5->getPluginId(), $item4->getPluginId()), + $this->assertMenuLink($item5['mlid'], array( + 'depth' => 2, + 'has_children' => 1, + 'p1' => $item4['mlid'], + 'p2' => $item5['mlid'], + 'p3' => 0, // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item2->getPluginId(), array( - 'children' => array($item3->getPluginId()), - 'parents' => array($item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()), + $this->assertMenuLink($item2['mlid'], array( + 'depth' => 3, + 'has_children' => 1, + 'p1' => $item4['mlid'], + 'p2' => $item5['mlid'], + 'p3' => $item2['mlid'], + 'p4' => 0, // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item3->getPluginId(), array( - 'children' => array(), - 'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()), + $this->assertMenuLink($item3['mlid'], array( + 'depth' => 4, + 'has_children' => 0, + 'p1' => $item4['mlid'], + 'p2' => $item5['mlid'], + 'p3' => $item2['mlid'], + 'p4' => $item3['mlid'], + 'p5' => 0, // See above. 'langcode' => 'en', )); @@ -376,39 +375,33 @@ function doMenuTests() { // item's weight doesn't get changed because of the old hardcoded delta=50. $items = array(); for ($i = -50; $i <= 51; $i++) { - $items[$i] = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name, TRUE, strval($i)); + $items[$i] = $this->addMenuLink(0, 'node/' . $node1->id(), $menu_name, TRUE, strval($i)); } - $this->assertMenuLink($items[51]->getPluginId(), array('weight' => '51')); + $this->assertMenuLink($items[51]['mlid'], array('weight' => '51')); - // Disable a link and then re-enable the link via the overview form. + // Enable a link via the overview form. $this->disableMenuLink($item1); $edit = array(); - $edit['links[menu_plugin_id:' . $item1->getPluginId() . '][enabled]'] = TRUE; - $this->drupalPostForm('admin/structure/menu/manage/' . $item1->getMenuName(), $edit, t('Save')); - - // Mark item2, item4 and item5 as expanded. - // This is done in order to show them on the frontpage. - $item2->expanded->value = 1; - $item2->save(); - $item4->expanded->value = 1; - $item4->save(); - $item5->expanded->value = 1; - $item5->save(); + + // Note in the UI the 'links[mlid:x][hidden]' form element maps to enabled, + // or NOT hidden. + $edit['links[mlid:' . $item1['mlid'] . '][hidden]'] = TRUE; + $this->drupalPostForm('admin/structure/menu/manage/' . $item1['menu_name'], $edit, t('Save')); // Verify in the database. - $this->assertMenuLink($item1->getPluginId(), array('hidden' => 0)); + $this->assertMenuLink($item1['mlid'], array('hidden' => 0)); // Add an external link. - $item7 = $this->addMenuLink('', 'http://drupal.org', $menu_name); - $this->assertMenuLink($item7->getPluginId(), array('url' => 'http://drupal.org')); + $item7 = $this->addMenuLink(0, 'http://drupal.org', $menu_name); + $this->assertMenuLink($item7['mlid'], array('link_path' => 'http://drupal.org', 'external' => 1)); // Add <front> menu item. - $item8 = $this->addMenuLink('', '<front>', $menu_name); - $this->assertMenuLink($item8->getPluginId(), array('route_name' => '<front>')); + $item8 = $this->addMenuLink(0, '<front>', $menu_name); + $this->assertMenuLink($item8['mlid'], array('link_path' => '<front>', 'external' => 1)); $this->drupalGet(''); $this->assertResponse(200); // Make sure we get routed correctly. - $this->clickLink($item8->getTitle()); + $this->clickLink($item8['link_title']); $this->assertResponse(200); // Save menu links for later tests. @@ -424,16 +417,16 @@ function testMenuQueryAndFragment() { // Make a path with query and fragment on. $path = 'test-page?arg1=value1&arg2=value2'; - $item = $this->addMenuLink('', $path); + $item = $this->addMenuLink(0, $path); - $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); - $this->assertFieldByName('url', $path, 'Path is found with both query and fragment.'); + $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit'); + $this->assertFieldByName('link_path', $path, 'Path is found with both query and fragment.'); // Now change the path to something without query and fragment. $path = 'test-page'; - $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', array('url' => $path), t('Save')); - $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); - $this->assertFieldByName('url', $path, 'Path no longer has query or fragment.'); + $this->drupalPostForm('admin/structure/menu/item/' . $item['mlid'] . '/edit', array('link_path' => $path), t('Save')); + $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit'); + $this->assertFieldByName('link_path', $path, 'Path no longer has query or fragment.'); } /** @@ -463,15 +456,15 @@ function testUnpublishedNodeMenuItem() { 'status' => NODE_NOT_PUBLISHED, )); - $item = $this->addMenuLink('', 'node/' . $node->id()); + $item = $this->addMenuLink(0, 'node/' . $node->id()); $this->modifyMenuLink($item); // Test that a user with 'administer menu' but without 'bypass node access' // cannot see the menu item. $this->drupalLogout(); $this->drupalLogin($this->admin_user); - $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName()); - $this->assertNoText($item->getTitle(), "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission"); + $this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']); + $this->assertNoText($item['link_title'], "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission"); } /** @@ -496,13 +489,43 @@ public function testBlockContextualLinks() { $this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="menu-ui-edit"><a href="' . base_path() . 'admin/structure/menu/manage/tools">Edit menu</a></li></ul>'); } + /** + * Tests menu link bundles. + */ + public function testMenuBundles() { + $this->drupalLogin($this->admin_user); + $menu = $this->addCustomMenu(); + // Clear the entity cache to ensure the static caches are rebuilt. + \Drupal::entityManager()->clearCachedBundles(); + $bundles = entity_get_bundles('menu_link'); + $this->assertTrue(isset($bundles[$menu->id()])); + $menus = menu_list_system_menus(); + $menus[$menu->id()] = $menu->label(); + ksort($menus); + $this->assertIdentical(array_keys($bundles), array_keys($menus)); + + // Test if moving a menu link between menus changes the bundle. + $node = $this->drupalCreateNode(array('type' => 'article')); + $item = $this->addMenuLink(0, 'node/' . $node->id(), 'tools'); + $this->moveMenuLink($item, 0, $menu->id()); + $this->assertEqual($item->bundle(), 'tools', 'Menu link bundle matches the menu'); + + $moved_item = entity_load('menu_link', $item->id(), TRUE); + $this->assertNotEqual($moved_item->bundle(), $item->bundle(), 'Menu link bundle was changed'); + $this->assertEqual($moved_item->bundle(), $menu->id(), 'Menu link bundle matches the menu'); + + $unsaved_item = entity_create('menu_link', array('menu_name' => $menu->id(), 'link_title' => $this->randomName(16), 'link_path' => '<front>')); + $this->assertEqual($unsaved_item->bundle(), $menu->id(), 'Unsaved menu link bundle matches the menu'); + $this->assertEqual($unsaved_item->menu_name, $menu->id(), 'Unsaved menu link menu name matches the menu'); + } + /** * Adds a menu link using the UI. * - * @param string $parent + * @param integer $plid * Optional parent menu link id. - * @param string $path - * The path to enter on the form. Defaults to the front page. + * @param string $link + * Link path. Defaults to the front page. * @param string $menu_name * Menu name. Defaults to 'tools'. * @param bool $expanded @@ -511,36 +534,40 @@ public function testBlockContextualLinks() { * to FALSE. * @param string $weight * Menu weight. Defaults to 0. + * @param string $actual_link + * Actual link path in case $link is an alias. * - * @return \Drupal\menu_link_content\Entity\MenuLinkContent + * @return \Drupal\menu_link\Entity\MenuLink * A menu link entity. */ - function addMenuLink($parent = '', $path = '<front>', $menu_name = 'tools', $expanded = FALSE, $weight = '0') { + function addMenuLink($plid = 0, $link = '<front>', $menu_name = 'tools', $expanded = TRUE, $weight = '0', $actual_link = FALSE) { // View add menu link page. $this->drupalGet("admin/structure/menu/manage/$menu_name/add"); $this->assertResponse(200); $title = '!link_' . $this->randomName(16); $edit = array( - 'url' => $path, - 'title[0][value]' => $title, - 'description[0][value]' => '', - 'enabled' => 1, - 'expanded[value]' => $expanded, - 'menu_parent' => $menu_name . ':' . $parent, - 'weight[0][value]' => $weight, + 'link_path' => $link, + 'link_title' => $title, + 'description' => '', + 'enabled' => TRUE, + 'expanded' => $expanded, + 'parent' => $menu_name . ':' . $plid, + 'weight' => $weight, ); + if (!$actual_link) { + $actual_link = $link; + } // Add menu link. $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertResponse(200); $this->assertText('The menu link has been saved.'); - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $title)); - + $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $title)); $menu_link = reset($menu_links); $this->assertTrue($menu_link, 'Menu link was found in database.'); - $this->assertMenuLink($menu_link->getPluginId(), array('menu_name' => $menu_name, 'children' => array(), 'parent' => $parent)); + $this->assertMenuLink($menu_link->id(), array('menu_name' => $menu_name, 'link_path' => $actual_link, 'has_children' => 0, 'plid' => $plid)); return $menu_link; } @@ -551,27 +578,27 @@ function addMenuLink($parent = '', $path = '<front>', $menu_name = 'tools', $exp function addInvalidMenuLink() { foreach (array('-&-', 'admin/people/permissions', '#') as $link_path) { $edit = array( - 'url' => $link_path, - 'title[0][value]' => 'title', + 'link_path' => $link_path, + 'link_title' => 'title', ); $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save')); - $this->assertRaw(t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $link_path)), 'Menu link was not created'); + $this->assertRaw(t("The path '@path' is either invalid or you do not have access to it.", array('@path' => $link_path)), 'Menu link was not created'); } } /** * Verifies a menu link using the UI. * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item + * @param array $item * Menu link. * @param object $item_node * Menu link content node. - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent + * @param array $parent * Parent menu link. * @param object $parent_node * Parent menu link content node. */ - function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $parent = NULL, $parent_node = NULL) { + function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL) { // View home page. $this->drupalGet(''); $this->assertResponse(200); @@ -579,7 +606,7 @@ function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $pare // Verify parent menu link. if (isset($parent)) { // Verify menu link. - $title = $parent->getTitle(); + $title = $parent['link_title']; $this->assertLink($title, 0, 'Parent menu link was displayed'); // Verify menu link link. @@ -589,7 +616,7 @@ function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $pare } // Verify menu link. - $title = $item->getTitle(); + $title = $item['link_title']; $this->assertLink($title, 0, 'Menu link was displayed'); // Verify menu link link. @@ -601,18 +628,18 @@ function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $pare /** * Changes the parent of a menu link using the UI. * - * @param \Drupal\menu_link_content\Entity\MenuLinkContentInterface $item + * @param array $item * The menu link item to move. - * @param int $parent + * @param int $plid * The id of the new parent. * @param string $menu_name * The menu the menu link will be moved to. */ - function moveMenuLink(MenuLinkContent $item, $parent, $menu_name) { - $mlid = $item->id(); + function moveMenuLink($item, $plid, $menu_name) { + $mlid = $item['mlid']; $edit = array( - 'menu_parent' => $menu_name . ':' . $parent, + 'parent' => $menu_name . ':' . $plid, ); $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); $this->assertResponse(200); @@ -621,54 +648,58 @@ function moveMenuLink(MenuLinkContent $item, $parent, $menu_name) { /** * Modifies a menu link using the UI. * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item - * Menu link entity. + * @param array $item + * Menu link passed by reference. */ - function modifyMenuLink(MenuLinkContent $item) { - $item->title->value = $this->randomName(16); + function modifyMenuLink(&$item) { + $item['link_title'] = $this->randomName(16); - $mlid = $item->id(); - $title = $item->getTitle(); + $mlid = $item['mlid']; + $title = $item['link_title']; // Edit menu link. $edit = array(); - $edit['title[0][value]'] = $title; + $edit['link_title'] = $title; $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); $this->assertResponse(200); $this->assertText('The menu link has been saved.'); // Verify menu link. - $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName()); + $this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']); $this->assertText($title, 'Menu link was edited'); } /** * Resets a standard menu link using the UI. * - * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link - * The Menu link. - * @param int $old_weight + * @param array $item + * Menu link. + * @param string $old_title * Original title for menu link. */ - function resetMenuLink(MenuLinkInterface $menu_link, $old_weight) { + function resetMenuLink($item, $old_title) { + $mlid = $item['mlid']; + $title = $item['link_title']; + // Reset menu link. - $this->drupalPostForm("admin/structure/menu/link/{$menu_link->getPluginId()}/reset", array(), t('Reset')); + $this->drupalPostForm("admin/structure/menu/item/$mlid/reset", array(), t('Reset')); $this->assertResponse(200); $this->assertRaw(t('The menu link was reset to its default settings.'), 'Menu link was reset'); // Verify menu link. - $instance = \Drupal::service('plugin.manager.menu.link')->createInstance($menu_link->getPluginId()); - $this->assertEqual($old_weight, $instance->getWeight(), 'Resets to the old weight.'); + $this->drupalGet(''); + $this->assertNoText($title, 'Menu link was reset'); + $this->assertText($old_title, 'Menu link was reset'); } /** * Deletes a menu link using the UI. * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item + * @param array $item * Menu link. */ - function deleteMenuLink(MenuLinkContent $item) { - $mlid = $item->id(); - $title = $item->getTitle(); + function deleteMenuLink($item) { + $mlid = $item['mlid']; + $title = $item['link_title']; // Delete menu link. $this->drupalPostForm("admin/structure/menu/item/$mlid/delete", array(), t('Confirm')); @@ -683,51 +714,51 @@ function deleteMenuLink(MenuLinkContent $item) { /** * Alternately disables and enables a menu link. * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item + * @param $item * Menu link. */ - function toggleMenuLink(MenuLinkContent $item) { + function toggleMenuLink($item) { $this->disableMenuLink($item); // Verify menu link is absent. $this->drupalGet(''); - $this->assertNoText($item->getTitle(), 'Menu link was not displayed'); + $this->assertNoText($item['link_title'], 'Menu link was not displayed'); $this->enableMenuLink($item); // Verify menu link is displayed. $this->drupalGet(''); - $this->assertText($item->getTitle(), 'Menu link was displayed'); + $this->assertText($item['link_title'], 'Menu link was displayed'); } /** * Disables a menu link. * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item + * @param $item * Menu link. */ - function disableMenuLink(MenuLinkContent $item) { - $mlid = $item->id(); + function disableMenuLink($item) { + $mlid = $item['mlid']; $edit['enabled'] = FALSE; $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); // Unlike most other modules, there is no confirmation message displayed. // Verify in the database. - $this->assertMenuLink($item->getPluginId(), array('hidden' => 1)); + $this->assertMenuLink($mlid, array('hidden' => 1)); } /** * Enables a menu link. * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item + * @param $item * Menu link. */ - function enableMenuLink(MenuLinkContent $item) { - $mlid = $item->id(); + function enableMenuLink($item) { + $mlid = $item['mlid']; $edit['enabled'] = TRUE; $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); // Verify in the database. - $this->assertMenuLink($item->getPluginId(), array('hidden' => 0)); + $this->assertMenuLink($mlid, array('hidden' => 0)); } /** @@ -751,19 +782,27 @@ public function testMenuParentsJsAccess() { /** * Returns standard menu link. * - * @return \Drupal\Core\Menu\MenuLinkInterface - * A menu link plugin. + * @return \Drupal\menu_link\Entity\MenuLink + * A menu link entity. */ private function getStandardMenuLink() { + $mlid = 0; // Retrieve menu link id of the Log out menu link, which will always be on // the front page. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $result = $menu_link_manager->loadLinksByRoute('user.logout'); - $instance = reset($result); + $query = \Drupal::entityQuery('menu_link') + ->condition('module', 'user') + ->condition('machine_name', 'user.logout'); + $result = $query->execute(); + if (!empty($result)) { + $mlid = reset($result); + } - $this->assertTrue((bool) $instance, 'Standard menu link was loaded'); - return $instance; + $this->assertTrue($mlid > 0, 'Standard menu link id was found'); + // Load menu link. + // Use api function so that link is translated for rendering. + $item = entity_load('menu_link', $mlid); + $this->assertTrue((bool) $item, 'Standard menu link was loaded'); + return $item; } /** @@ -794,9 +833,9 @@ private function verifyAccess($response = 200) { $this->assertText(t('Tools'), 'Tools menu page was displayed'); } - // View menu edit page for a static link. + // View menu edit page. $item = $this->getStandardMenuLink(); - $this->drupalGet('admin/structure/menu/link/' . $item->getPluginId() . '/edit'); + $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Edit menu item'), 'Menu edit page was displayed'); diff --git a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php index 60691974c1a3..5f16536e2155 100644 --- a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php +++ b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php @@ -19,58 +19,28 @@ abstract class MenuWebTestBase extends WebTestBase { * * @var array */ - public static $modules = array('menu_ui', 'menu_link_content'); + public static $modules = array('menu_ui'); /** * Fetchs the menu item from the database and compares it to expected item. * - * @param int $menu_plugin_id + * @param int $mlid * Menu item id. - * @param array $expected_item + * @param array $item * Array containing properties to verify. */ - function assertMenuLink($menu_plugin_id, array $expected_item) { + function assertMenuLink($mlid, array $expected_item) { // Retrieve menu link. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $menu_link_manager->resetDefinitions(); - // Reset the static load cache. - \Drupal::entityManager()->getStorage('menu_link_content')->resetCache(); - $definition = $menu_link_manager->getDefinition($menu_plugin_id); - - $entity = NULL; - - // Pull the path from the menu link content. - if (strpos($menu_plugin_id, 'menu_link_content') === 0) { - list(, $uuid) = explode(':', $menu_plugin_id, 2); - /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $entity */ - $entity = \Drupal::entityManager()->loadEntityByUuid('menu_link_content', $uuid); - } - - if (isset($expected_item['children'])) { - $child_ids = array_values($menu_link_manager->getChildIds($menu_plugin_id)); - sort($expected_item['children']); - if ($child_ids) { - sort($child_ids); - } - $this->assertEqual($expected_item['children'], $child_ids); - unset($expected_item['children']); + $item = entity_load('menu_link', $mlid); + $options = $item->options; + if (!empty($options['query'])) { + $item['link_path'] .= '?' . \Drupal::urlGenerator()->httpBuildQuery($options['query']); } - - if (isset($expected_item['parents'])) { - $parent_ids = array_values($menu_link_manager->getParentIds($menu_plugin_id)); - $this->assertEqual($expected_item['parents'], $parent_ids); - unset($expected_item['parents']); - } - - if (isset($expected_item['langcode']) && $entity) { - $this->assertEqual($entity->langcode->value, $expected_item['langcode']); - unset($expected_item['langcode']); + if (!empty($options['fragment'])) { + $item['link_path'] .= '#' . $options['fragment']; } - foreach ($expected_item as $key => $value) { - $this->assertTrue(isset($definition[$key])); - $this->assertEqual($definition[$key], $value); + $this->assertEqual($item[$key], $value); } } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 56154d17816b..dd47b47c7ce7 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -452,10 +452,10 @@ function hook_node_validate(\Drupal\node\NodeInterface $node, $form, &$form_stat * @ingroup entity_crud */ function hook_node_submit(\Drupal\node\NodeInterface $node, $form, &$form_state) { - // Decompose the selected menu parent option into 'menu_name' and 'parent', if + // Decompose the selected menu parent option into 'menu_name' and 'plid', if // the form used the default parent selection widget. if (!empty($form_state['values']['menu']['parent'])) { - list($node->menu['menu_name'], $node->menu['parent']) = explode(':', $form_state['values']['menu']['parent']); + list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']); } } diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php index 288cf4d82a1d..69e4235f7c43 100644 --- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php +++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php @@ -214,7 +214,7 @@ function testInstallConfig() { */ function testEnableModulesFixedList() { // Install system module. - $this->container->get('module_handler')->install(array('system', 'menu_link_content')); + $this->container->get('module_handler')->install(array('system', 'menu_link')); $entity_manager = \Drupal::entityManager(); // entity_test is loaded via $modules; its entity type should exist. diff --git a/core/modules/system/config/install/system.menu.yml b/core/modules/system/config/install/system.menu.yml new file mode 100644 index 000000000000..e73636c82322 --- /dev/null +++ b/core/modules/system/config/install/system.menu.yml @@ -0,0 +1 @@ +active_menus_default: [] diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 2e9219d42b1b..537552443957 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -168,6 +168,17 @@ system.logging: type: string label: 'Error messages to display' +system.menu: + type: mapping + label: 'Menu settings' + mapping: + active_menus_default: + type: sequence + label: 'Active menus' + sequence: + - type: string + label: 'Menu' + system.performance: type: mapping label: 'Performance settings' diff --git a/core/modules/system/src/Controller/AdminController.php b/core/modules/system/src/Controller/AdminController.php index d9800f057afd..7445d52b143a 100644 --- a/core/modules/system/src/Controller/AdminController.php +++ b/core/modules/system/src/Controller/AdminController.php @@ -36,7 +36,7 @@ public function index() { // Sort links by title. uasort($admin_tasks, array('\Drupal\Component\Utility\SortArray', 'sortByTitleElement')); // Move 'Configure permissions' links to the bottom of each section. - $permission_key = "user.admin_permissions.$module"; + $permission_key = "user.admin.people.permissions.$module"; if (isset($admin_tasks[$permission_key])) { $permission_task = $admin_tasks[$permission_key]; unset($admin_tasks[$permission_key]); diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php index c811e85c7267..b9609687ca4a 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -12,8 +12,6 @@ use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Form\FormBuilderInterface; -use Drupal\Core\Menu\MenuLinkTreeInterface; -use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Theme\ThemeAccessCheck; use Drupal\system\SystemManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -58,13 +56,6 @@ class SystemController extends ControllerBase { */ protected $themeHandler; - /** - * The menu link tree service. - * - * @var \Drupal\Core\Menu\MenuLinkTreeInterface - */ - protected $menuLinkTree; - /** * Constructs a new SystemController. * @@ -78,16 +69,13 @@ class SystemController extends ControllerBase { * The form builder. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. - * @param \Drupal\Core\Menu\MenuLinkTreeInterface - * The menu link tree service. */ - public function __construct(SystemManager $systemManager, QueryFactory $queryFactory, ThemeAccessCheck $theme_access, FormBuilderInterface $form_builder, ThemeHandlerInterface $theme_handler, MenuLinkTreeInterface $menu_link_tree) { + public function __construct(SystemManager $systemManager, QueryFactory $queryFactory, ThemeAccessCheck $theme_access, FormBuilderInterface $form_builder, ThemeHandlerInterface $theme_handler) { $this->systemManager = $systemManager; $this->queryFactory = $queryFactory; $this->themeAccess = $theme_access; $this->formBuilder = $form_builder; $this->themeHandler = $theme_handler; - $this->menuLinkTree = $menu_link_tree; } /** @@ -99,49 +87,66 @@ public static function create(ContainerInterface $container) { $container->get('entity.query'), $container->get('access_check.theme'), $container->get('form_builder'), - $container->get('theme_handler'), - $container->get('menu.link_tree') + $container->get('theme_handler') ); } /** * Provide the administration overview page. * - * @param string $link_id - * The ID of the administrative path link for which to display child links. + * @param string $path + * The administrative path for which to display child links. * * @return array * A renderable array of the administration overview page. */ - public function overview($link_id) { + public function overview($path) { // Check for status report errors. if ($this->systemManager->checkRequirements() && $this->currentUser()->hasPermission('administer site configuration')) { drupal_set_message($this->t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'error'); } - // Load all menu links below it. - $parameters = new MenuTreeParameters(); - $parameters->setRoot($link_id)->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks(); - $tree = $this->menuLinkTree->load(NULL, $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - ); - $tree = $this->menuLinkTree->transform($tree, $manipulators); $blocks = array(); - foreach ($tree as $key => $element) { - $link = $element->link; - $block['title'] = $link->getTitle(); - $block['description'] = $link->getDescription(); - $block['content'] = array( - '#theme' => 'admin_block_content', - '#content' => $this->systemManager->getAdminBlock($link), - ); + // Load all links on $path and menu links below it. + $query = $this->queryFactory->get('menu_link') + ->condition('link_path', $path) + ->condition('module', 'system'); + $result = $query->execute(); + $menu_link_storage = $this->entityManager()->getStorage('menu_link'); + if ($system_link = $menu_link_storage->loadMultiple($result)) { + $system_link = reset($system_link); + $query = $this->queryFactory->get('menu_link') + ->condition('link_path', 'admin/help', '<>') + ->condition('menu_name', $system_link->menu_name) + ->condition('plid', $system_link->id()) + ->condition('hidden', 0); + $result = $query->execute(); + if (!empty($result)) { + $menu_links = $menu_link_storage->loadMultiple($result); + foreach ($menu_links as $item) { + _menu_link_translate($item); + if (!$item['access']) { + continue; + } + // The link description, either derived from 'description' in hook_menu() + // or customized via Menu UI module is used as title attribute. + if (!empty($item['localized_options']['attributes']['title'])) { + $item['description'] = $item['localized_options']['attributes']['title']; + unset($item['localized_options']['attributes']['title']); + } + $block = $item; + $block['content'] = array( + '#theme' => 'admin_block_content', + '#content' => $this->systemManager->getAdminBlock($item), + ); - if (!empty($block['content']['#content'])) { - $blocks[$key] = $block; + if (!empty($block['content']['#content'])) { + // Prepare for sorting as in function _menu_tree_check_access(). + // The weight is offset so it is always positive, with a uniform 5-digits. + $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block; + } + } } } - if ($blocks) { ksort($blocks); return array( diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index 2c5dd06b6365..e25070f9f570 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -9,20 +9,18 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; -use Drupal\Core\Controller\TitleResolverInterface; use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\Query\QueryFactory; +use Drupal\Core\Entity\Query\QueryFactoryInterface; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface; -use Drupal\Core\Menu\MenuLinkManagerInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\Request; /** * Provides module installation interface. @@ -63,18 +61,11 @@ class ModulesListForm extends FormBase { protected $entityManager; /** - * The title resolver. + * The query factory. * - * @var \Drupal\Core\Controller\TitleResolverInterface + * @var \Drupal\Core\Entity\Query\QueryFactory */ - protected $titleResolver; - - /** - * The route provider. - * - * @var \Drupal\Core\Routing\RouteProviderInterface - */ - protected $routeProvider; + protected $queryFactory; /** * The current route match. @@ -83,13 +74,6 @@ class ModulesListForm extends FormBase { */ protected $routeMatch; - /** - * The menu link manager. - * - * @var \Drupal\Core\Menu\MenuLinkManagerInterface - */ - protected $menuLinkManager; - /** * {@inheritdoc} */ @@ -99,11 +83,9 @@ public static function create(ContainerInterface $container) { $container->get('keyvalue.expirable')->get('module_list'), $container->get('access_manager'), $container->get('entity.manager'), + $container->get('entity.query'), $container->get('current_user'), - $container->get('current_route_match'), - $container->get('title_resolver'), - $container->get('router.route_provider'), - $container->get('plugin.manager.menu.link') + $container->get('current_route_match') ); } @@ -118,27 +100,21 @@ public static function create(ContainerInterface $container) { * Access manager. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory + * The entity query factory. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The current route match. - * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver - * The title resolver. - * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider - * The route provider. - * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager - * The menu link manager. */ - public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManagerInterface $access_manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, RouteMatchInterface $route_match, TitleResolverInterface $title_resolver, RouteProviderInterface $route_provider, MenuLinkManagerInterface $menu_link_manager) { + public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManagerInterface $access_manager, EntityManagerInterface $entity_manager, QueryFactory $query_factory, AccountInterface $current_user, RouteMatchInterface $route_match) { $this->moduleHandler = $module_handler; $this->keyValueExpirable = $key_value_expirable; $this->accessManager = $access_manager; $this->entityManager = $entity_manager; + $this->queryFactory = $query_factory; $this->currentUser = $current_user; $this->routeMatch = $route_match; - $this->titleResolver = $title_resolver; - $this->routeProvider = $route_provider; - $this->menuLinkManager = $menu_link_manager; } /** @@ -273,23 +249,11 @@ protected function buildRow(array $modules, Extension $module, $distribution) { if ($module->status && isset($module->info['configure'])) { $route_parameters = isset($module->info['configure_parameters']) ? $module->info['configure_parameters'] : array(); if ($this->accessManager->checkNamedRoute($module->info['configure'], $route_parameters, $this->currentUser)) { - - $links = $this->menuLinkManager->loadLinksByRoute($module->info['configure']); - /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ - $link = reset($links); - // Most configure links have a corresponding menu link, though some just - // have a route. - if ($link) { - $description = $link->getDescription(); - } - else { - $request = new Request(); - $request->attributes->set('_route_name', $module->info['configure']); - $route_object = $this->routeProvider->getRouteByName($module->info['configure']); - $request->attributes->set('_route', $route_object); - $description = $this->titleResolver->getTitle($request, $route_object); - } - + $result = $this->queryFactory->get('menu_link') + ->condition('route_name', $module->info['configure']) + ->execute(); + $menu_items = $this->entityManager->getStorage('menu_link')->loadMultiple($result); + $item = reset($menu_items); $row['links']['configure'] = array( '#type' => 'link', '#title' => $this->t('Configure'), @@ -298,7 +262,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) { '#options' => array( 'attributes' => array( 'class' => array('module-link', 'module-link-configure'), - 'title' => $description, + 'title' => $item['description'], ), ), ); diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php index 258ffbb2d770..538147145e61 100644 --- a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php +++ b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php @@ -10,8 +10,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\block\BlockBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\Core\Menu\MenuActiveTrailInterface; -use Drupal\Core\Menu\MenuLinkTreeInterface; +use Drupal\menu_link\MenuTreeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -28,19 +27,12 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface { /** - * The menu link tree service. + * The menu tree. * - * @var \Drupal\Core\Menu\MenuLinkTreeInterface + * @var \Drupal\menu_link\MenuTreeInterface */ protected $menuTree; - /** - * The active menu trail service. - * - * @var \Drupal\Core\Menu\MenuActiveTrailInterface - */ - protected $menuActiveTrail; - /** * Constructs a new SystemMenuBlock. * @@ -50,15 +42,12 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa * The plugin_id for the plugin instance. * @param array $plugin_definition * The plugin implementation definition. - * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree - * The menu tree service. - * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail - * The active menu trail service. + * @param \Drupal\menu_link\MenuTreeInterface $menu_tree + * The menu tree. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuTreeInterface $menu_tree) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->menuTree = $menu_tree; - $this->menuActiveTrail = $menu_active_trail; } /** @@ -69,8 +58,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('menu.link_tree'), - $container->get('menu.active_trail') + $container->get('menu_link.tree') ); } @@ -78,15 +66,8 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function build() { - $menu_name = $this->getDerivativeId(); - $parameters = $this->menuTree->getCurrentRouteMenuTreeParameters($menu_name); - $tree = $this->menuTree->load($menu_name, $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - ); - $tree = $this->menuTree->transform($tree, $manipulators); - return $this->menuTree->build($tree); + $menu = $this->getDerivativeId(); + return $this->menuTree->renderMenu($menu); } /** @@ -110,7 +91,9 @@ public function defaultConfiguration() { public function getCacheKeys() { // Add a key for the active menu trail. $menu = $this->getDerivativeId(); - return array_merge(parent::getCacheKeys(), array($this->menuActiveTrail->getActiveTrailCacheKey($menu))); + $active_trail = $this->menuTree->getActiveTrailIds($menu); + $active_trail_key = 'trail.' . implode('|', $active_trail); + return array_merge(parent::getCacheKeys(), array($active_trail_key)); } /** @@ -131,7 +114,7 @@ public function getCacheTags() { protected function getRequiredCacheContexts() { // Menu blocks must be cached per role: different roles may have access to // different menu links. - return array('cache_context.user.roles', 'cache_context.language'); + return array('cache_context.user.roles'); } } diff --git a/core/modules/system/src/SystemManager.php b/core/modules/system/src/SystemManager.php index 00f97aae4e9c..696efc1e297a 100644 --- a/core/modules/system/src/SystemManager.php +++ b/core/modules/system/src/SystemManager.php @@ -6,11 +6,8 @@ namespace Drupal\system; +use Drupal\Component\Utility\Unicode; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Menu\MenuActiveTrailInterface; -use Drupal\Core\Menu\MenuLinkTreeInterface; -use Drupal\Core\Menu\MenuLinkInterface; -use Drupal\Core\Menu\MenuTreeParameters; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -36,25 +33,18 @@ class SystemManager { protected $database; /** - * The request stack. + * The menu link storage. * - * @var \Symfony\Component\HttpFoundation\RequestStack + * @var \Drupal\menu_link\MenuLinkStorageInterface */ - protected $requestStack; + protected $menuLinkStorage; /** - * The menu link tree manager. - * - * @var \Drupal\Core\Menu\MenuLinkTreeInterface - */ - protected $menuTree; - - /** - * The active menu trail service. + * The request stack. * - * @var \Drupal\Core\Menu\MenuActiveTrailInterface + * @var \Symfony\Component\HttpFoundation\RequestStack */ - protected $menuActiveTrail; + protected $requestStack; /** * A static cache of menu items. @@ -89,17 +79,12 @@ class SystemManager { * The entity manager. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack * The request stack. - * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree - * The menu tree manager. - * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail - * The active menu trail service. */ - public function __construct(ModuleHandlerInterface $module_handler, Connection $database, EntityManagerInterface $entity_manager, RequestStack $request_stack, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) { + public function __construct(ModuleHandlerInterface $module_handler, Connection $database, EntityManagerInterface $entity_manager, RequestStack $request_stack) { $this->moduleHandler = $module_handler; $this->database = $database; + $this->menuLinkStorage = $entity_manager->getStorage('menu_link'); $this->requestStack = $request_stack; - $this->menuTree = $menu_tree; - $this->menuActiveTrail = $menu_active_trail; } /** @@ -186,10 +171,11 @@ public function getMaxSeverity(&$requirements) { * A render array suitable for drupal_render. */ public function getBlockContents() { - // We hard-code the menu name here since otherwise a link in the tools menu - // or elsewhere could give us a blank block. - $link = $this->menuActiveTrail->getActiveLink('admin'); - if ($link && $content = $this->getAdminBlock($link)) { + $request = $this->requestStack->getCurrentRequest(); + $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME); + $items = $this->menuLinkStorage->loadByProperties(array('route_name' => $route_name)); + $item = reset($items); + if ($content = $this->getAdminBlock($item)) { $output = array( '#theme' => 'admin_block_content', '#content' => $content, @@ -206,33 +192,48 @@ public function getBlockContents() { /** * Provide a single block on the administration overview page. * - * @param \Drupal\Core\Menu\MenuLinkInterface $instance + * @param \Drupal\menu_link\MenuLinkInterface|array $item * The menu item to be displayed. * * @return array * An array of menu items, as expected by theme_admin_block_content(). */ - public function getAdminBlock(MenuLinkInterface $instance) { + public function getAdminBlock($item) { + if (!isset($item['mlid'])) { + $menu_links = $this->menuLinkStorage->loadByProperties(array('link_path' => $item['path'], 'module' => 'system')); + if ($menu_links) { + $menu_link = reset($menu_links); + $item['mlid'] = $menu_link->id(); + $item['menu_name'] = $menu_link->menu_name; + } + else { + return array(); + } + } + + if (isset($this->menuItems[$item['mlid']])) { + return $this->menuItems[$item['mlid']]; + } + $content = array(); - // Only find the children of this link. - $link_id = $instance->getPluginId(); - $parameters = new MenuTreeParameters(); - $parameters->setRoot($link_id)->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks(); - $tree = $this->menuTree->load(NULL, $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - ); - $tree = $this->menuTree->transform($tree, $manipulators); - foreach ($tree as $key => $element) { - /** @var $link \Drupal\Core\Menu\MenuLinkInterface */ - $link = $element->link; - $content[$key]['title'] = $link->getTitle(); - $content[$key]['options'] = $link->getOptions(); - $content[$key]['description'] = $link->getDescription(); - $content[$key]['url'] = $link->getUrlObject(); + $menu_links = $this->menuLinkStorage->loadByProperties(array('plid' => $item['mlid'], 'menu_name' => $item['menu_name'], 'hidden' => 0)); + foreach ($menu_links as $link) { + _menu_link_translate($link); + if ($link['access']) { + // The link description, either derived from 'description' in + // hook_menu() or customized via Menu UI module is used as title attribute. + if (!empty($link['localized_options']['attributes']['title'])) { + $link['description'] = $link['localized_options']['attributes']['title']; + unset($link['localized_options']['attributes']['title']); + } + // Prepare for sorting as in function _menu_tree_check_access(). + // The weight is offset so it is always positive, with a uniform 5-digits. + $key = (50000 + $link['weight']) . ' ' . Unicode::strtolower($link['title']) . ' ' . $link['mlid']; + $content[$key] = $link; + } } ksort($content); + $this->menuItems[$item['mlid']] = $content; return $content; } diff --git a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php index ec6201aa89fe..f1bf64041ae8 100644 --- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php +++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php @@ -83,10 +83,15 @@ function testBreadCrumbs() { ); $this->assertBreadcrumb('admin/structure/menu/manage/tools', $trail); + $mlid_node_add = \Drupal::entityQuery('menu_link') + ->condition('machine_name', 'node.add_page') + ->condition('module', 'node') + ->execute(); + $mlid_node_add = reset($mlid_node_add); $trail += array( 'admin/structure/menu/manage/tools' => t('Tools'), ); - $this->assertBreadcrumb("admin/structure/menu/link/node.add_page/edit", $trail); + $this->assertBreadcrumb("admin/structure/menu/item/$mlid_node_add/edit", $trail); $this->assertBreadcrumb('admin/structure/menu/manage/tools/add', $trail); // Verify Node administration breadcrumbs. @@ -160,7 +165,7 @@ function testBreadCrumbs() { // Alter node type menu settings. \Drupal::config("menu.entity.node.$type") ->set('available_menus', $menus) - ->set('parent', 'tools:') + ->set('parent', 'tools:0') ->save(); foreach ($menus as $menu) { @@ -169,13 +174,13 @@ function testBreadCrumbs() { $node2 = $this->drupalCreateNode(array( 'type' => $type, 'title' => $title, - 'menu' => array( - 'hidden' => 0, - 'title' => 'Parent ' . $title, + 'menu' => entity_create('menu_link', array( + 'enabled' => 1, + 'link_title' => 'Parent ' . $title, 'description' => '', 'menu_name' => $menu, - 'parent' => '', - ), + 'plid' => 0, + )), )); if ($menu == 'tools') { @@ -187,26 +192,26 @@ function testBreadCrumbs() { // link below it, and verify a full breadcrumb for the last child node. $menu = 'tools'; $edit = array( - 'title[0][value]' => 'Root', - 'url' => 'node', + 'link_title' => 'Root', + 'link_path' => 'node', ); $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => 'Root')); + $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => 'Root')); $link = reset($menu_links); $edit = array( - 'menu[menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(), + 'menu[parent]' => $link['menu_name'] . ':' . $link['mlid'], ); $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save and keep published')); $expected = array( - "node" => $link->getTitle(), + "node" => $link['link_title'], ); $trail = $home + $expected; $tree = $expected + array( - 'node/' . $parent->id() => $parent->menu['title'], + 'node/' . $parent->id() => $parent->menu['link_title'], ); $trail += array( - 'node/' . $parent->id() => $parent->menu['title'], + 'node/' . $parent->id() => $parent->menu['link_title'], ); // Add a taxonomy term/tag to last node, and add a link for that term to the @@ -236,36 +241,32 @@ function testBreadCrumbs() { } $parent_tid = $term->id(); } - $parent_mlid = ''; + $parent_mlid = 0; foreach ($tags as $name => $data) { $term = $data['term']; $edit = array( - 'title[0][value]' => "$name link", - 'url' => "taxonomy/term/{$term->id()}", - 'menu_parent' => "$menu:{$parent_mlid}", + 'link_title' => "$name link", + 'link_path' => "taxonomy/term/{$term->id()}", + 'parent' => "$menu:{$parent_mlid}", ); $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $edit['title[0][value]'], 'route_name' => 'taxonomy.term_page', 'route_parameters' => serialize(array('taxonomy_term' => $term->id())))); + $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $edit['link_title'], 'link_path' => $edit['link_path'])); $tags[$name]['link'] = reset($menu_links); - $parent_mlid = $tags[$name]['link']->getPluginId(); + $tags[$name]['link']['link_path'] = $edit['link_path']; + $parent_mlid = $tags[$name]['link']['mlid']; } // Verify expected breadcrumbs for menu links. $trail = $home; $tree = array(); - // Logout the user because we want to check the active class as well, which - // is just rendered as anonymous user. - $this->drupalLogout(); foreach ($tags as $name => $data) { $term = $data['term']; - /** @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface $link */ $link = $data['link']; - $link_path = $link->getUrlObject()->getInternalPath(); $tree += array( - $link_path => $link->getTitle(), + $link['link_path'] => $link['link_title'], ); - $this->assertBreadcrumb($link_path, $trail, $term->getName(), $tree); + $this->assertBreadcrumb($link['link_path'], $trail, $term->getName(), $tree); $this->assertRaw(String::checkPlain($parent->getTitle()), 'Tagged node found.'); // Additionally make sure that this link appears only once; i.e., the @@ -274,14 +275,14 @@ function testBreadCrumbs() { // other than the breadcrumb trail. $elements = $this->xpath('//div[@id=:menu]/descendant::a[@href=:href]', array( ':menu' => 'block-bartik-tools', - ':href' => url($link_path), + ':href' => url($link['link_path']), )); - $this->assertTrue(count($elements) == 1, "Link to {$link_path} appears only once."); + $this->assertTrue(count($elements) == 1, "Link to {$link['link_path']} appears only once."); // Next iteration should expect this tag as parent link. // Note: Term name, not link name, due to taxonomy_term_page(). $trail += array( - $link_path => $term->getName(), + $link['link_path'] => $term->getName(), ); } @@ -291,6 +292,7 @@ function testBreadCrumbs() { user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array( 'access user profiles', )); + $this->drupalLogout(); // Verify breadcrumb on front page. $this->assertBreadcrumb('<front>', array()); @@ -357,5 +359,4 @@ function testBreadCrumbs() { $this->assertBreadcrumb('admin/reports/dblog', $trail, t('Recent log messages')); $this->assertNoResponse(403); } - } diff --git a/core/modules/system/src/Tests/Menu/LinksTest.php b/core/modules/system/src/Tests/Menu/LinksTest.php index 552b1ae08bc7..eecfb8c2ce25 100644 --- a/core/modules/system/src/Tests/Menu/LinksTest.php +++ b/core/modules/system/src/Tests/Menu/LinksTest.php @@ -7,7 +7,6 @@ namespace Drupal\system\Tests\Menu; -use Drupal\Component\Utility\String; use Drupal\locale\TranslationString; use Drupal\simpletest\WebTestBase; @@ -15,9 +14,6 @@ * Tests handling of menu links hierarchies. * * @group Menu - * - * @todo Move this under menu_link_content module. - * https://www.drupal.org/node/2310353 */ class LinksTest extends WebTestBase { @@ -26,14 +22,7 @@ class LinksTest extends WebTestBase { * * @var array */ - public static $modules = array('router_test', 'menu_link_content'); - - /** - * The menu link plugin manager. - * - * @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager - */ - protected $menuLinkManager; + public static $modules = array('router_test'); /** * {@inheritdoc} @@ -41,8 +30,6 @@ class LinksTest extends WebTestBase { public function setUp() { parent::setUp(); - $this->menuLinkManager = \Drupal::service('plugin.manager.menu.link'); - entity_create('menu', array( 'id' => 'menu_test', 'label' => 'Test menu', @@ -54,60 +41,55 @@ public function setUp() { * Create a simple hierarchy of links. */ function createLinkHierarchy($module = 'menu_test') { - // First remove all the menu links in the menu. - $this->menuLinkManager->deleteLinksInMenu('menu_test'); + // First remove all the menu links. + $menu_links = menu_link_load_multiple(); + menu_link_delete_multiple(array_keys($menu_links), TRUE, TRUE); // Then create a simple link hierarchy: - // - parent - // - child-1 - // - child-1-1 - // - child-1-2 - // - child-2 + // - $parent + // - $child-1 + // - $child-1-1 + // - $child-1-2 + // - $child-2 $base_options = array( - 'title' => 'Menu link test', - 'provider' => $module, + 'link_title' => 'Menu link test', + 'module' => $module, 'menu_name' => 'menu_test', - 'bundle' => 'menu_link_content' ); - $parent = $base_options + array( - 'route_name' => 'menu_test.hierarchy_parent', + $links['parent'] = $base_options + array( + 'link_path' => 'menu-test/parent', ); - $link = entity_create('menu_link_content', $parent); - $link->save(); - $links['parent'] = $link->getPluginId(); + $links['parent'] = entity_create('menu_link', $links['parent']); + $links['parent']->save(); - $child_1 = $base_options + array( - 'route_name' => 'menu_test.hierarchy_parent_child', - 'parent' => $links['parent'], + $links['child-1'] = $base_options + array( + 'link_path' => 'menu-test/parent/child-1', + 'plid' => $links['parent']['mlid'], ); - $link = entity_create('menu_link_content', $child_1); - $link->save(); - $links['child-1'] = $link->getPluginId(); + $links['child-1'] = entity_create('menu_link', $links['child-1']); + $links['child-1']->save(); - $child_1_1 = $base_options + array( - 'route_name' => 'menu_test.hierarchy_parent_child2', - 'parent' => $links['child-1'], + $links['child-1-1'] = $base_options + array( + 'link_path' => 'menu-test/parent/child-1/child-1-1', + 'plid' => $links['child-1']['mlid'], ); - $link = entity_create('menu_link_content', $child_1_1); - $link->save(); - $links['child-1-1'] = $link->getPluginId(); + $links['child-1-1'] = entity_create('menu_link', $links['child-1-1']); + $links['child-1-1']->save(); - $child_1_2 = $base_options + array( - 'route_name' => 'menu_test.hierarchy_parent_child2', - 'parent' => $links['child-1'], + $links['child-1-2'] = $base_options + array( + 'link_path' => 'menu-test/parent/child-1/child-1-2', + 'plid' => $links['child-1']['mlid'], ); - $link = entity_create('menu_link_content', $child_1_2); - $link->save(); - $links['child-1-2'] = $link->getPluginId(); + $links['child-1-2'] = entity_create('menu_link', $links['child-1-2']); + $links['child-1-2']->save(); - $child_2 = $base_options + array( - 'route_name' => 'menu_test.hierarchy_parent_child', - 'parent' => $links['parent'], + $links['child-2'] = $base_options + array( + 'link_path' => 'menu-test/parent/child-2', + 'plid' => $links['parent']['mlid'], ); - $link = entity_create('menu_link_content', $child_2); - $link->save(); - $links['child-2'] = $link->getPluginId(); + $links['child-2'] = entity_create('menu_link', $links['child-2']); + $links['child-2']->save(); return $links; } @@ -116,12 +98,13 @@ function createLinkHierarchy($module = 'menu_test') { * Assert that at set of links is properly parented. */ function assertMenuLinkParents($links, $expected_hierarchy) { - foreach ($expected_hierarchy as $id => $parent) { - /* @var \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin */ - $menu_link_plugin = $this->menuLinkManager->createInstance($links[$id]); - $expected_parent = isset($links[$parent]) ? $links[$parent] : ''; + foreach ($expected_hierarchy as $child => $parent) { + $mlid = $links[$child]['mlid']; + $plid = $parent ? $links[$parent]['mlid'] : 0; - $this->assertEqual($menu_link_plugin->getParent(), $expected_parent, String::format('Menu link %id has parent of %parent, expected %expected_parent.', array('%id' => $id, '%parent' => $menu_link_plugin->getParent(), '%expected_parent' => $expected_parent))); + $menu_link = menu_link_load($mlid); + menu_link_save($menu_link); + $this->assertEqual($menu_link['plid'], $plid, format_string('Menu link %mlid has parent of %plid, expected %expected_plid.', array('%mlid' => $mlid, '%plid' => $menu_link['plid'], '%expected_plid' => $plid))); } } @@ -133,7 +116,7 @@ function testMenuLinkReparenting($module = 'menu_test') { $links = $this->createLinkHierarchy($module); $expected_hierarchy = array( - 'parent' => '', + 'parent' => FALSE, 'child-1' => 'parent', 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', @@ -144,15 +127,11 @@ function testMenuLinkReparenting($module = 'menu_test') { // Start over, and move child-1 under child-2, and check that all the // childs of child-1 have been moved too. $links = $this->createLinkHierarchy($module); - /* @var \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin */ - $this->menuLinkManager->updateDefinition($links['child-1'], array('parent' => $links['child-2'])); - // Verify that the entity was updated too. - $menu_link_plugin = $this->menuLinkManager->createInstance($links['child-1']); - $entity = \Drupal::entityManager()->loadEntityByUuid('menu_link_content', $menu_link_plugin->getDerivativeId()); - $this->assertEqual($entity->getParentId(), $links['child-2']); + $links['child-1']['plid'] = $links['child-2']['mlid']; + menu_link_save($links['child-1']); $expected_hierarchy = array( - 'parent' => '', + 'parent' => FALSE, 'child-1' => 'child-2', 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', @@ -161,9 +140,29 @@ function testMenuLinkReparenting($module = 'menu_test') { $this->assertMenuLinkParents($links, $expected_hierarchy); // Start over, and delete child-1, and check that the children of child-1 - // have been reassigned to the parent. + // have been reassigned to the parent. menu_link_delete() will cowardly + // refuse to delete a menu link defined by the system module, so skip the + // test in that case. + if ($module != 'system') { + $links = $this->createLinkHierarchy($module); + menu_link_delete($links['child-1']['mlid']); + + $expected_hierarchy = array( + 'parent' => FALSE, + 'child-1-1' => 'parent', + 'child-1-2' => 'parent', + 'child-2' => 'parent', + ); + $this->assertMenuLinkParents($links, $expected_hierarchy); + } + + // Start over, forcefully delete child-1 from the database, simulating a + // database crash. Check that the children of child-1 have been reassigned + // to the parent, going up on the old path hierarchy stored in each of the + // links. $links = $this->createLinkHierarchy($module); - $this->menuLinkManager->removeDefinition($links['child-1']); + // Don't do that at home. + entity_delete_multiple('menu_link', array($links['child-1']['mlid'])); $expected_hierarchy = array( 'parent' => FALSE, @@ -173,26 +172,119 @@ function testMenuLinkReparenting($module = 'menu_test') { ); $this->assertMenuLinkParents($links, $expected_hierarchy); - // @todo Figure out what makes sense to test in terms of automatic - // re-parenting. https://www.drupal.org/node/2309531 + // Start over, forcefully delete the parent from the database, simulating a + // database crash. Check that the children of parent are now top-level. + $links = $this->createLinkHierarchy($module); + // Don't do that at home. + db_delete('menu_links') + ->condition('mlid', $links['parent']['mlid']) + ->execute(); + + $expected_hierarchy = array( + 'child-1-1' => 'child-1', + 'child-1-2' => 'child-1', + 'child-2' => FALSE, + ); + $this->assertMenuLinkParents($links, $expected_hierarchy); + } + + /** + * Tests automatic reparenting. + * + * Runs tests on menu links defined by the menu_link.static service. + */ + function testMenuLinkRouterReparenting() { + // Run all the standard parenting tests on menu links derived from + // menu routers. + $this->testMenuLinkReparenting('system'); + + // Additionnaly, test reparenting based on path. + $links = $this->createLinkHierarchy('system'); + + // Move child-1-2 has a child of child-2, making the link hierarchy + // inconsistent with the path hierarchy. + $links['child-1-2']['plid'] = $links['child-2']['mlid']; + menu_link_save($links['child-1-2']); + + // Check the new hierarchy. + $expected_hierarchy = array( + 'parent' => FALSE, + 'child-1' => 'parent', + 'child-1-1' => 'child-1', + 'child-2' => 'parent', + 'child-1-2' => 'child-2', + ); + $this->assertMenuLinkParents($links, $expected_hierarchy); + + // Now delete 'parent' directly from the database, simulating a database + // crash. 'child-1' and 'child-2' should get moved to the + // top-level. + // Don't do that at home. + db_delete('menu_links') + ->condition('mlid', $links['parent']['mlid']) + ->execute(); + $expected_hierarchy = array( + 'child-1' => FALSE, + 'child-1-1' => 'child-1', + 'child-2' => FALSE, + 'child-1-2' => 'child-2', + ); + $this->assertMenuLinkParents($links, $expected_hierarchy); + + // Now delete 'child-2' directly from the database, simulating a database + // crash. 'child-1-2' will get reparented to the top. + // Don't do that at home. + db_delete('menu_links') + ->condition('mlid', $links['child-2']['mlid']) + ->execute(); + $expected_hierarchy = array( + 'child-1' => FALSE, + 'child-1-1' => 'child-1', + 'child-1-2' => FALSE, + ); + $this->assertMenuLinkParents($links, $expected_hierarchy); + } + + /** + * Tests the router system integration (route_name and route_parameters). + */ + public function testRouterIntegration() { + $menu_link = entity_create('menu_link', array( + 'link_path' => 'router_test/test1', + )); + $menu_link->save(); + $this->assertEqual($menu_link->route_name, 'router_test.1'); + $this->assertEqual($menu_link->route_parameters, array()); + + $menu_link = entity_create('menu_link', array( + 'link_path' => 'router_test/test3/test', + )); + $menu_link->save(); + $this->assertEqual($menu_link->route_name, 'router_test.3'); + $this->assertEqual($menu_link->route_parameters, array('value' => 'test')); + + $menu_link = entity_load('menu_link', $menu_link->id()); + $this->assertEqual($menu_link->route_name, 'router_test.3'); + $this->assertEqual($menu_link->route_parameters, array('value' => 'test')); } /** - * Tests uninstalling a module providing default links. + * Tests uninstall a module providing default links. */ public function testModuleUninstalledMenuLinks() { \Drupal::moduleHandler()->install(array('menu_test')); \Drupal::service('router.builder')->rebuild(); - \Drupal::service('plugin.manager.menu.link')->rebuild(); - $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_test.menu_test'); + menu_link_rebuild_defaults(); + $result = $menu_link = \Drupal::entityQuery('menu_link')->condition('machine_name', 'menu_test')->execute(); + $menu_links = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($result); $this->assertEqual(count($menu_links), 1); $menu_link = reset($menu_links); - $this->assertEqual($menu_link->getPluginId(), 'menu_test'); + $this->assertEqual($menu_link->machine_name, 'menu_test'); // Uninstall the module and ensure the menu link got removed. \Drupal::moduleHandler()->uninstall(array('menu_test')); - \Drupal::service('plugin.manager.menu.link')->rebuild(); - $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_test.menu_test'); + $result = $menu_link = \Drupal::entityQuery('menu_link')->condition('machine_name', 'menu_test')->execute(); + $menu_links = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($result); $this->assertEqual(count($menu_links), 0); } diff --git a/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php b/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php deleted file mode 100644 index 607887511980..000000000000 --- a/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\system\Tests\Menu\MenuLinkDefaultIntegrationTest. - */ - -namespace Drupal\system\Tests\Menu; - -use Drupal\Core\Menu\MenuTreeParameters; -use Drupal\simpletest\KernelTestBase; - -/** - * Tests integration of static menu links. - * - * @group Menu - */ -class MenuLinkDefaultIntegrationTest extends KernelTestBase { - - /** - * Modules to enable. - * - * @var array - */ - public static $modules = array( - 'system', - 'menu_test', - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - $this->installSchema('system', array('router')); - } - - /** - * Tests moving a static menu link without a specified menu to the root. - */ - public function testMoveToRoot() { - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $menu_link_manager->rebuild(); - - $menu_link = $menu_link_manager->getDefinition('menu_test.child'); - $this->assertEqual($menu_link['parent'], 'menu_test.parent'); - $this->assertEqual($menu_link['menu_name'], 'test'); - - $tree = \Drupal::menuTree()->load('test', new MenuTreeParameters()); - $this->assertEqual(count($tree), 1); - $this->assertEqual($tree['menu_test.parent']->link->getPluginId(), 'menu_test.parent'); - $this->assertEqual($tree['menu_test.parent']->subtree['menu_test.child']->link->getPluginId(), 'menu_test.child'); - - // Ensure that the menu name is not forgotten. - $menu_link_manager->updateDefinition('menu_test.child', array('parent' => '')); - $menu_link = $menu_link_manager->getDefinition('menu_test.child'); - - $this->assertEqual($menu_link['parent'], ''); - $this->assertEqual($menu_link['menu_name'], 'test'); - - $tree = \Drupal::menuTree()->load('test', new MenuTreeParameters()); - $this->assertEqual(count($tree), 2); - $this->assertEqual($tree['menu_test.parent']->link->getPluginId(), 'menu_test.parent'); - $this->assertEqual($tree['menu_test.child']->link->getPluginId(), 'menu_test.child'); - - $this->assertTrue(TRUE); - } - -} diff --git a/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php b/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php index 98b968520633..1b97c98ff41c 100644 --- a/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php +++ b/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php @@ -43,6 +43,7 @@ class MenuLinkTreeTest extends KernelTestBase { public static $modules = array( 'system', 'menu_test', + 'menu_link', 'menu_link_content', 'field', ); diff --git a/core/modules/system/src/Tests/Menu/MenuRouterRebuildTest.php b/core/modules/system/src/Tests/Menu/MenuRouterRebuildTest.php new file mode 100644 index 000000000000..e56796f12219 --- /dev/null +++ b/core/modules/system/src/Tests/Menu/MenuRouterRebuildTest.php @@ -0,0 +1,51 @@ +<?php + +/** + * @file + * Contains \Drupal\system\Tests\Menu\MenuRouterRebuildTest. + */ + +namespace Drupal\system\Tests\Menu; + +use Drupal\simpletest\WebTestBase; +use Drupal\Core\Language\Language; + +/** + * Tests menu_router_rebuild(). + * + * @group Menu + */ +class MenuRouterRebuildTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('language', 'menu_test'); + + /** + * {@inheritdoc} + */ + function setUp() { + parent::setUp(); + + $language = new Language(array('id' => 'nl')); + language_save($language); + } + + /** + * Tests configuration context when rebuilding the menu router table. + */ + public function testMenuRouterRebuildContext() { + // Enter a language context before rebuilding the menu router tables. + \Drupal::languageManager()->setConfigOverrideLanguage(language_load('nl')); + \Drupal::service('router.builder')->rebuild(); + + // Check that the language context was not used for building the menu item. + $menu_items = \Drupal::entityManager()->getStorage('menu_link')->loadByProperties(array('route_name' => 'menu_test.context')); + $menu_item = reset($menu_items); + $this->assertTrue($menu_item['link_title'] == 'English', 'Config context overrides are ignored when rebuilding menu router items.'); + } + +} diff --git a/core/modules/system/src/Tests/Menu/MenuRouterTest.php b/core/modules/system/src/Tests/Menu/MenuRouterTest.php index 270badce8664..26a0beb848af 100644 --- a/core/modules/system/src/Tests/Menu/MenuRouterTest.php +++ b/core/modules/system/src/Tests/Menu/MenuRouterTest.php @@ -52,7 +52,11 @@ public function testMenuIntegration() { $this->doTestMenuOptionalPlaceholders(); $this->doTestMenuOnRoute(); $this->doTestMenuName(); - $this->doTestMenuLinksDiscoveredAlter(); + $this->doTestMenuLinkDefaultsAlter(); + $this->doTestMenuItemTitlesCases(); + $this->doTestMenuLinkMaintain(); + $this->doTestMenuLinkOptions(); + $this->doTestMenuItemHooks(); $this->doTestHookMenuIntegration(); $this->doTestExoticPath(); } @@ -104,61 +108,183 @@ protected function doTestDescriptionMenuItems() { $this->assertText(t('Menu item description text')); } + /** + * Tests for menu_link_maintain(). + */ + protected function doTestMenuLinkMaintain() { + $admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($admin_user); + + // Create three menu items. + menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/1', 'Menu link #1'); + menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/1', 'Menu link #1-main'); + menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/2', 'Menu link #2'); + + // Move second link to the main-menu, to test caching later on. + $menu_links_to_update = entity_load_multiple_by_properties('menu_link', array('link_title' => 'Menu link #1-main', 'customized' => 0, 'module' => 'menu_test')); + foreach ($menu_links_to_update as $menu_link) { + $menu_link->menu_name = 'main'; + $menu_link->save(); + } + + // Load front page. + $this->drupalGet(''); + $this->assertLink('Menu link #1'); + $this->assertLink('Menu link #1-main'); + $this->assertLink('Menu link #2'); + + // Rename all links for the given path. + menu_link_maintain('menu_test', 'update', 'menu_test_maintain/1', 'Menu link updated'); + // Load a different page to be sure that we have up to date information. + $this->drupalGet('menu_test_maintain/1'); + $this->assertLink('Menu link updated'); + $this->assertNoLink('Menu link #1'); + $this->assertNoLink('Menu link #1-main'); + $this->assertLink('Menu link #2'); + + // Delete all links for the given path. + menu_link_maintain('menu_test', 'delete', 'menu_test_maintain/1', ''); + // Load a different page to be sure that we have up to date information. + $this->drupalGet('menu_test_maintain/2'); + $this->assertNoLink('Menu link updated'); + $this->assertNoLink('Menu link #1'); + $this->assertNoLink('Menu link #1-main'); + $this->assertLink('Menu link #2'); + } + /** * Tests for menu_name parameter for default menu links. */ protected function doTestMenuName() { $admin_user = $this->drupalCreateUser(array('administer site configuration')); $this->drupalLogin($admin_user); - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test'); + + $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test')); $menu_link = reset($menu_links); - $this->assertEqual($menu_link->getMenuName(), 'original', 'Menu name is "original".'); + $this->assertEqual($menu_link->menu_name, 'original', 'Menu name is "original".'); // Change the menu_name parameter in menu_test.module, then force a menu // rebuild. menu_test_menu_name('changed'); - $menu_link_manager->rebuild(); + \Drupal::service('router.builder')->rebuild(); - $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test'); + $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test')); $menu_link = reset($menu_links); - $this->assertEqual($menu_link->getMenuName(), 'changed', 'Menu name was successfully changed after rebuild.'); + $this->assertEqual($menu_link->menu_name, 'changed', 'Menu name was successfully changed after rebuild.'); } /** - * Tests menu links added in hook_menu_links_discovered_alter(). + * Tests menu links added in hook_menu_link_defaults_alter(). */ - protected function doTestMenuLinksDiscoveredAlter() { + protected function doTestMenuLinkDefaultsAlter() { // Check that machine name does not need to be defined since it is already // set as the key of each menu link. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.custom'); + $menu_links = entity_load_multiple_by_properties('menu_link', array('route_name' => 'menu_test.custom')); $menu_link = reset($menu_links); - $this->assertEqual($menu_link->getPluginId(), 'menu_test.custom', 'Menu links added at hook_menu_links_discovered_alter() obtain the machine name from the $links key.'); + $this->assertEqual($menu_link->machine_name, 'menu_test.custom', 'Menu links added at hook_menu_link_defaults_alter() obtain the machine name from the $links key.'); // Make sure that rebuilding the menu tree does not produce duplicates of - // links added by hook_menu_links_discovered_alter(). + // links added by hook_menu_link_defaults_alter(). \Drupal::service('router.builder')->rebuild(); $this->drupalGet('menu-test'); - $this->assertUniqueText('Custom link', 'Menu links added by hook_menu_links_discovered_alter() do not duplicate after a menu rebuild.'); + $this->assertUniqueText('Custom link', 'Menu links added by hook_menu_link_defaults_alter() do not duplicate after a menu rebuild.'); } /** * Tests for menu hierarchy. */ protected function doTestMenuHierarchy() { - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent'); - $parent_link = reset($menu_links); - $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent.child'); - $child_link = reset($menu_links); - $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent.child2.child'); - $unattached_child_link = reset($menu_links); - - $this->assertEqual($child_link->getParent(), $parent_link->getPluginId(), 'The parent of a directly attached child is correct.'); - $this->assertEqual($unattached_child_link->getParent(), $parent_link->getPluginId(), 'The parent of a non-directly attached child is correct.'); + $parent_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent')); + $parent_link = reset($parent_links); + $child_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child')); + $child_link = reset($child_links); + $unattached_child_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child2/child')); + $unattached_child_link = reset($unattached_child_links); + + $this->assertEqual($child_link['plid'], $parent_link['mlid'], 'The parent of a directly attached child is correct.'); + $this->assertEqual($unattached_child_link['plid'], $parent_link['mlid'], 'The parent of a non-directly attached child is correct.'); + } + + /** + * Test menu maintenance hooks. + */ + protected function doTestMenuItemHooks() { + // Create an item. + menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/4', 'Menu link #4'); + $this->assertEqual(menu_test_static_variable(), 'insert', 'hook_menu_link_insert() fired correctly'); + // Update the item. + menu_link_maintain('menu_test', 'update', 'menu_test_maintain/4', 'Menu link updated'); + $this->assertEqual(menu_test_static_variable(), 'update', 'hook_menu_link_update() fired correctly'); + // Delete the item. + menu_link_maintain('menu_test', 'delete', 'menu_test_maintain/4', ''); + $this->assertEqual(menu_test_static_variable(), 'delete', 'hook_menu_link_delete() fired correctly'); + } + + /** + * Test menu link 'options' storage and rendering. + */ + protected function doTestMenuLinkOptions() { + // Create a menu link with options. + $menu_link = entity_create('menu_link', array( + 'link_title' => 'Menu link options test', + 'link_path' => 'test-page', + 'module' => 'menu_test', + 'options' => array( + 'attributes' => array( + 'title' => 'Test title attribute', + ), + 'query' => array( + 'testparam' => 'testvalue', + ), + ), + )); + menu_link_save($menu_link); + + // Load front page. + $this->drupalGet('test-page'); + $this->assertRaw('title="Test title attribute"', 'Title attribute of a menu link renders.'); + $this->assertRaw('testparam=testvalue', 'Query parameter added to menu link.'); + } + + /** + * Tests the possible ways to set the title for menu items. + * Also tests that menu item titles work with string overrides. + */ + protected function doTestMenuItemTitlesCases() { + + // Build array with string overrides. + $test_data = array( + 1 => array('Example title - Case 1' => 'Alternative example title - Case 1'), + 2 => array('Example title' => 'Alternative example title'), + 3 => array('Example title' => 'Alternative example title'), + ); + + foreach ($test_data as $case_no => $override) { + $this->menuItemTitlesCasesHelper($case_no); + $this->addCustomTranslations('en', array('' => $override)); + $this->writeCustomTranslations(); + + $this->menuItemTitlesCasesHelper($case_no, TRUE); + $this->addCustomTranslations('en', array()); + $this->writeCustomTranslations(); + } + } + + /** + * Get a URL and assert the title given a case number. If override is true, + * the title is asserted to begin with "Alternative". + */ + protected function menuItemTitlesCasesHelper($case_no, $override = FALSE) { + $this->drupalGet('menu-title-test/case' . $case_no); + $this->assertResponse(200); + $asserted_title = $override ? 'Alternative example title - Case ' . $case_no : 'Example title - Case ' . $case_no; + $this->assertTitle($asserted_title . ' | Drupal', format_string('Menu title is: %title.', array('%title' => $asserted_title)), 'Menu'); + } + + /** + * Load the router for a given path. + */ + protected function menuLoadRouter($router_path) { + return db_query('SELECT * FROM {menu_router} WHERE path = :path', array(':path' => $router_path))->fetchAssoc(); } /** diff --git a/core/modules/system/src/Tests/System/AdminTest.php b/core/modules/system/src/Tests/System/AdminTest.php index f5609c718b44..950194f577c5 100644 --- a/core/modules/system/src/Tests/System/AdminTest.php +++ b/core/modules/system/src/Tests/System/AdminTest.php @@ -7,7 +7,6 @@ namespace Drupal\system\Tests\System; -use Drupal\Core\Menu\MenuTreeParameters; use Drupal\simpletest\WebTestBase; /** @@ -63,10 +62,9 @@ function testAdminPages() { // Verify that all visible, top-level administration links are listed on // the main administration page. foreach ($this->getTopLevelMenuLinks() as $item) { - $this->assertLink($item->getTitle()); - $this->assertLinkByHref($item->getUrlObject()->toString()); - // The description should appear below the link. - $this->assertText($item->getDescription()); + $this->assertLink($item['title']); + $this->assertLinkByHref($item['link_path']); + $this->assertText($item['localized_options']['attributes']['title']); } // For each administrative listing page on which the Locale module appears, @@ -121,28 +119,26 @@ function testAdminPages() { /** * Returns all top level menu links. * - * @return \Drupal\Core\Menu\MenuLinkInterface[] + * @return \Drupal\menu_link\MenuLinkInterface[] */ protected function getTopLevelMenuLinks() { - $menu_tree = \Drupal::menuTree(); - - // The system.admin link is normally the parent of all top-level admin links. - $parameters = new MenuTreeParameters(); - $parameters->setRoot('system.admin')->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks(); - $tree = $menu_tree->load(NULL, $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:flatten'), - ); - $tree = $menu_tree->transform($tree, $manipulators); - - // Transform the tree to a list of menu links. - $menu_links = array(); - foreach ($tree as $element) { - $menu_links[] = $element->link; + $route_provider = \Drupal::service('router.route_provider'); + $routes = array(); + foreach ($route_provider->getAllRoutes() as $key => $value) { + $path = $value->getPath(); + if (strpos($path, '/admin/') === 0 && count(explode('/', $path)) == 3) { + $routes[$key] = $key; + } } + $menu_link_ids = \Drupal::entityQuery('menu_link') + ->condition('route_name', $routes) + ->execute(); - return $menu_links; + $menu_items = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($menu_link_ids); + foreach ($menu_items as &$menu_item) { + _menu_link_translate($menu_item); + } + return $menu_items; } /** diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 1fa53e88c5ee..71b0cec7b580 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -59,7 +59,7 @@ function template_preprocess_admin_block_content(&$variables) { $variables['attributes']['class'][] = 'compact'; } foreach ($variables['content'] as $key => $item) { - $variables['content'][$key]['link'] = \Drupal::linkGenerator()->generateFromUrl($item['title'], $item['url']); + $variables['content'][$key]['link'] = l($item['title'], $item['link_path'], $item['localized_options']); if (!$compact && isset($item['description'])) { $variables['content'][$key]['description'] = Xss::filterAdmin($item['description']); } diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index d78942e945f6..7fc912bf483f 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -397,13 +397,13 @@ function hook_page_build(&$page) { } /** - * Alters all the menu links discovered by the menu link plugin manager. + * Alter links for menus. * * @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 + * An array of default 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 @@ -440,7 +440,7 @@ function hook_page_build(&$page) { * * @ingroup menu */ -function hook_menu_links_discovered_alter(&$links) { +function hook_menu_link_defaults_alter(&$links) { // Change the weight and title of the user.logout link. $links['user.logout']['weight'] = -10; $links['user.logout']['title'] = 'Logout'; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 8bbb5e77eb59..fc2c63f84ccf 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -11,7 +11,6 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\TranslationWrapper; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Menu\MenuTreeParameters; use Drupal\block\BlockPluginInterface; use Drupal\user\UserInterface; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -1456,48 +1455,76 @@ function system_admin_compact_mode() { * An array of task links. */ function system_get_module_admin_tasks($module, array $info) { - $tree = &drupal_static(__FUNCTION__); - - $menu_tree = \Drupal::menuTree(); - - if (!isset($tree)) { - $parameters = new MenuTreeParameters(); - $parameters->setRoot('system.admin')->excludeRoot()->excludeHiddenLinks(); - $tree = $menu_tree->load('system.admin', $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - array('callable' => 'menu.default_tree_manipulators:flatten'), - ); - $tree = $menu_tree->transform($tree, $manipulators); + $links = &drupal_static(__FUNCTION__); + + if (!isset($links)) { + $links = array(); + $menu_links = entity_get_controller('menu_link')->loadModuleAdminTasks(); + foreach ($menu_links as $link) { + _menu_link_translate($link); + if ($link['access']) { + $links[$link['machine_name']] = $link; + } + } } $admin_tasks = array(); - foreach ($tree as $element) { - $link = $element->link; - if ($link->getProvider() != $module) { + $titles = array(); + foreach ($links as $item) { + if ($item['module'] != $module) { continue; } - $admin_tasks[] = array( - 'title' => $link->getTitle(), - 'description' => $link->getDescription(), - 'url' => $link->getUrlObject(), - ); + $machine_name = $item['machine_name']; + if (isset($links[$machine_name])) { + $task = $links[$machine_name]; + // The link description, either derived from 'description' in the default + // menu link or customized via Menu UI module is used as title attribute. + if (!empty($task['localized_options']['attributes']['title'])) { + $task['description'] = $task['localized_options']['attributes']['title']; + unset($task['localized_options']['attributes']['title']); + } + + // Check the admin tasks for duplicate names. If one is found, + // append the parent menu item's title to differentiate. + $duplicate_path = array_search($task['title'], $titles); + if ($duplicate_path !== FALSE) { + if ($parent = menu_link_load($task['plid'])) { + // Append the parent item's title to this task's title. + $task['title'] = t('@original_title (@parent_title)', array('@original_title' => $task['title'], '@parent_title' => $parent['title'])); + } + if ($parent = menu_link_load($admin_tasks[$duplicate_path]['plid'])) { + // Append the parent item's title to the duplicated task's title. + // We use $links[$duplicate_path] in case there are triplicates. + $admin_tasks[$duplicate_path]['title'] = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]['title'], '@parent_title' => $parent['title'])); + } + } + else { + $titles[$machine_name] = $task['title']; + } + + $admin_tasks[$machine_name] = $task; + } } // Append link for permissions. if (\Drupal::moduleHandler()->implementsHook($module, 'permission')) { /** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */ $access_manager = \Drupal::service('access_manager'); + /** @var \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage */ + $menu_link_storage = \Drupal::entityManager() + ->getStorage('menu_link'); if ($access_manager->checkNamedRoute('user.admin_permissions', array(), \Drupal::currentUser())) { - /** @var \Drupal\Core\Url $url */ - $url = new \Drupal\Core\Url('user.admin_permissions'); - $url->setOption('fragment', 'module-' . $module); - $admin_tasks["user.admin_permissions.$module"] = array( + $path = \Drupal::urlGenerator() + ->getPathFromRoute('user.admin_permissions'); + $options = array(); + $options['fragment'] = 'module-' . $module; + $menu_link = $menu_link_storage->create(array( + 'route_name' => 'user.admin_permissions', + 'link_path' => $path, 'title' => t('Configure @module permissions', array('@module' => $info['name'])), - 'description' => '', - 'url' => $url, - ); + 'localized_options' => $options + )); + $admin_tasks["user.admin.people.permissions.$module"] = $menu_link; } } diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index c22d93bad1b8..35e17f7bd2a4 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -382,7 +382,7 @@ system.admin_config: path: '/admin/config' defaults: _content: '\Drupal\system\Controller\SystemController::overview' - link_id: 'system.admin_config' + path: 'admin/config' _title: 'Configuration' requirements: _permission: 'access administration pages' @@ -414,7 +414,7 @@ system.admin_content: path: '/admin/content' defaults: _content: '\Drupal\system\Controller\SystemController::overview' - link_id: 'system.admin_content' + path: 'admin/content' _title: 'Content' requirements: _permission: 'access administration pages' diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index 9e09d193d816..2b7046cb0ec0 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -5,7 +5,7 @@ services: - { name: access_check, applies_to: _access_system_cron } system.manager: class: Drupal\system\SystemManager - arguments: ['@module_handler', '@database', '@entity.manager', '@request_stack', '@menu.link_tree', '@menu.active_trail'] + arguments: ['@module_handler', '@database', '@entity.manager', '@request_stack'] system.breadcrumb.default: class: Drupal\system\PathBasedBreadcrumbBuilder arguments: ['@router.request_context', '@access_manager', '@router', '@path_processor_manager', '@config.factory', '@title_resolver', '@current_user'] diff --git a/core/modules/system/templates/menu-tree.html.twig b/core/modules/system/templates/menu-tree.html.twig deleted file mode 100644 index 03acca6d20a0..000000000000 --- a/core/modules/system/templates/menu-tree.html.twig +++ /dev/null @@ -1,40 +0,0 @@ -{# -/** - * @file - * Default theme implementation for a menu tree. - * - * Available variables: - * - attributes: Attributes for the UL containing the tree of links. - * - tree: Menu tree to be output. - * - heading: (optional) A heading to precede the links. - * - text: The heading text. - * - level: The heading level (e.g. 'h2', 'h3'). - * - attributes: (optional) A keyed list of attributes for the heading. - * If the heading is a string, it will be used as the text of the heading and - * the level will default to 'h2'. - * - * Headings should be used on navigation menus and any list of links that - * consistently appears on multiple pages. To make the heading invisible use - * the 'visually-hidden' CSS class. Do not use 'display:none', which - * removes it from screen-readers and assistive technology. Headings allow - * screen-reader and keyboard only users to navigate to or skip the links. - * See http://juicystudio.com/article/screen-readers-display-none.php and - * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. - * - * @see template_preprocess_menu_tree() - * - * @ingroup themeable - */ -#} -{% if tree -%} - {%- if heading -%} - {%- if heading.level -%} - <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}> - {%- else -%} - <h2{{ heading.attributes }}>{{ heading.text }}</h2> - {%- endif -%} - {%- endif -%} - <ul{{ attributes }}> - {{ tree }} - </ul> -{%- endif %} diff --git a/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml b/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml index 914fbd5e1fd3..d9e7a39ad6ae 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml +++ b/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml @@ -69,13 +69,3 @@ menu_test.menu-title-test.case3: menu_test.context: title: '' route_name: menu_test.context - -menu_test.parent: - title: 'Test menu_name parent' - route_name: menu_test.menu_name_test - menu_name: test - -menu_test.child: - title: 'Test menu_name child' - route_name: menu_test.menu_name_test - parent: menu_test.parent diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module index b4f76a2dd53b..c222ce805b1a 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -5,10 +5,12 @@ * Module that implements various hooks for menu tests. */ +use Drupal\menu_link\Entity\MenuLink; + /** - * Implements hook_menu_links_discovered_alter(). + * Implements hook_menu_link_defaults_alter(). */ -function menu_test_menu_links_discovered_alter(&$links) { +function menu_test_menu_link_defaults_alter(&$links) { // Many of the machine names here are slightly different from the route name. // Since the machine name is arbitrary, this helps ensure that core does not // add mistaken assumptions about the correlation. @@ -94,6 +96,29 @@ function menu_test_callback() { return 'This is menu_test_callback().'; } +/** + * Page callback: Tests menu_test_menu_tree_set_path(). + * + * Retrieves the current menu path and if the menu path is not empty updates + * the menu path that is used to determine the active menu trail. + * + * @return string + * A string that can be used for comparison. + * + * @see menu_test_menu(). + * + * @deprecated Use \Drupal\menu_test\Controller\MenuTestController::menuTrail() + */ +function menu_test_menu_trail_callback() { + $menu_path = \Drupal::state()->get('menu_test.menu_tree_set_path') ?: array(); + /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu_link.tree'); + if (!empty($menu_path)) { + $menu_tree->setPath($menu_path['menu_name'], $menu_path['path']); + } + return 'This is menu_test_menu_trail_callback().'; +} + /** * Page callback: Tests the theme negotiation functionality. * @@ -141,6 +166,44 @@ function menu_test_menu_name($new_name = '') { return $menu_name; } +/** + * Implements hook_ENTITY_TYPE_insert() for menu_link entities. + */ +function menu_test_menu_link_insert(MenuLink $item) { + menu_test_static_variable('insert'); +} + +/** + * Implements hook_ENTITY_TYPE_update() for menu_link entities. + */ +function menu_test_menu_link_update(MenuLink $item) { + menu_test_static_variable('update'); +} + +/** + * Implements hook_ENTITY_TYPE_delete() for menu_link entities. + */ +function menu_test_menu_link_delete(MenuLink $item) { + menu_test_static_variable('delete'); +} + +/** + * Sets a static variable for testing hook results. + * + * @param null|string $value + * (optional) The value to set or NULL to return the current value. + * + * @return null|string + * A text string for comparison to test assertions. + */ +function menu_test_static_variable($value = NULL) { + static $variable; + if (!empty($value)) { + $variable = $value; + } + return $variable; +} + /** * Title callback: Concatenates the title and case number. * diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.links.menu.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.links.menu.yml deleted file mode 100644 index 291fd7038f24..000000000000 --- a/core/modules/system/tests/modules/test_page_test/test_page_test.links.menu.yml +++ /dev/null @@ -1,4 +0,0 @@ -test_page_test.test_page: - route_name: test_page_test.test_page - title: 'Test front page link' - weight: 0 diff --git a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php index c01ac7194604..a227a7fe2fb7 100644 --- a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php +++ b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php @@ -125,16 +125,30 @@ function testModuleStatusChangeSubtreesHashCacheClear() { } /** - * Tests toolbar cache tags implementation. + * Tests toolbar_menu_link_update() hook implementation. */ function testMenuLinkUpdateSubtreesHashCacheClear() { - // The ID of a (any) admin menu link. - $admin_menu_link_id = 'system.admin_config_development'; + // Get subtree items for the admin menu. + $query = \Drupal::entityQuery('menu_link'); + for ($i = 1; $i <= 3; $i++) { + $query->sort('p' . $i, 'ASC'); + } + $query->condition('menu_name', 'admin'); + $query->condition('depth', '2', '>='); + + // Build an ordered array of links using the query result object. + $links = array(); + if ($result = $query->execute()) { + $links = menu_link_load_multiple($result); + } + // Get the first link in the set. + $links = array_values($links); + $link = array_shift($links); // Disable the link. $edit = array(); $edit['enabled'] = FALSE; - $this->drupalPostForm("admin/structure/menu/link/" . $admin_menu_link_id . "/edit", $edit, t('Save')); + $this->drupalPostForm("admin/structure/menu/item/" . $link['mlid'] . "/edit", $edit, t('Save')); $this->assertResponse(200); $this->assertText('The menu link has been saved.'); diff --git a/core/modules/toolbar/toolbar.info.yml b/core/modules/toolbar/toolbar.info.yml index 6967bf65726b..269f7eccf084 100644 --- a/core/modules/toolbar/toolbar.info.yml +++ b/core/modules/toolbar/toolbar.info.yml @@ -6,3 +6,4 @@ package: Core version: VERSION dependencies: - breakpoint + - menu_link diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index d27a89e1c2c3..d8e536339808 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -6,13 +6,13 @@ */ use Drupal\Core\Cache\Cache; -use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Template\Attribute; use Drupal\Component\Datetime\DateTimePlus; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\String; +use Drupal\menu_link\MenuLinkInterface; use Drupal\user\RoleInterface; use Drupal\user\UserInterface; @@ -326,6 +326,8 @@ function toolbar_pre_render_item($element) { * Implements hook_toolbar(). */ function toolbar_toolbar() { + $items = array(); + // The 'Home' tab is a simple link, with no corresponding tray. $items['home'] = array( '#type' => 'toolbar_item', @@ -350,13 +352,33 @@ function toolbar_toolbar() { '#weight' => -20, ); + // Retrieve the administration menu from the database. + $tree = toolbar_get_menu_tree(); + + // Add attributes to the links before rendering. + toolbar_menu_navigation_links($tree); + + /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu_link.tree'); + + $menu = array( + '#heading' => t('Administration menu'), + 'toolbar_administration' => array( + '#type' => 'container', + '#attributes' => array( + 'class' => array('toolbar-menu-administration'), + ), + 'administration_menu' => $menu_tree->renderTree($tree), + ), + ); + // To conserve bandwidth, we only include the top-level links in the HTML. // The subtrees are fetched through a JSONP script that is generated at the // toolbar_subtrees route. We provide the JavaScript requesting that JSONP // script here with the hash parameter that is needed for that route. // @see toolbar_subtrees_jsonp() $langcode = \Drupal::languageManager()->getCurrentLanguage()->id; - $subtrees_attached['js'][] = array( + $menu['toolbar_administration']['#attached']['js'][] = array( 'type' => 'setting', 'data' => array('toolbar' => array( 'subtreesHash' => _toolbar_get_subtrees_hash($langcode), @@ -383,19 +405,7 @@ function toolbar_toolbar() { 'data-drupal-subtrees' => '', ), ), - 'tray' => array( - '#heading' => t('Administration menu'), - '#attached' => $subtrees_attached, - 'toolbar_administration' => array( - '#pre_render' => array( - 'toolbar_prerender_toolbar_administration_tray', - ), - '#type' => 'container', - '#attributes' => array( - 'class' => array('toolbar-menu-administration'), - ), - ), - ), + 'tray' => $menu, '#weight' => -15, ); @@ -403,102 +413,90 @@ function toolbar_toolbar() { } /** - * Renders the toolbar's administration tray. + * Gets only the top level items below the 'admin' path. * - * @param array $element - * A renderable array. - * - * @return array - * The updated renderable array. - * - * @see drupal_render() + * @return + * An array containing a menu tree of top level items below the 'admin' path. */ -function toolbar_prerender_toolbar_administration_tray(array $element) { - $menu_tree = \Drupal::menuTree(); - // Render the top-level administration menu links. - $parameters = new MenuTreeParameters(); - $parameters->setRoot('system.admin')->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks(); - $tree = $menu_tree->load(NULL, $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - array('callable' => 'toolbar_menu_navigation_links'), - ); - $tree = $menu_tree->transform($tree, $manipulators); - $element['administration_menu'] = $menu_tree->build($tree); - return $element; +function toolbar_get_menu_tree() { + $tree = array(); + /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu_link.tree'); + $query = \Drupal::entityQuery('menu_link') + ->condition('menu_name', 'admin') + ->condition('module', 'system') + ->condition('link_path', 'admin'); + $result = $query->execute(); + if (!empty($result)) { + $admin_link = menu_link_load(reset($result)); + $tree = $menu_tree->buildTree('admin', array( + 'expanded' => array($admin_link['mlid']), + 'min_depth' => $admin_link['depth'] + 1, + 'max_depth' => $admin_link['depth'] + 1, + )); + } + + return $tree; } /** - * Adds toolbar-specific attributes to the menu link tree. + * Generates an array of links from a menu tree array. * - * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree - * The menu link tree to manipulate. + * Based on menu_navigation_links(). Adds path based IDs and icon placeholders + * to the links. * - * @return \Drupal\Core\Menu\MenuLinkTreeElement[] - * The manipulated menu link tree. + * @return + * An array of links as defined above. */ -function toolbar_menu_navigation_links(array $tree) { - foreach ($tree as $element) { - if ($element->subtree) { - toolbar_menu_navigation_links($element->subtree); +function toolbar_menu_navigation_links(&$tree) { + foreach ($tree as $key => $item) { + // Configure sub-items. + if (!empty($item['below'])) { + toolbar_menu_navigation_links($tree[$key]['below']); } - // Make sure we have a path specific ID in place, so we can attach icons - // and behaviors to the menu links. - $link = $element->link; - $url = $link->getUrlObject(); - if ($url->isExternal()) { - // This is an unusual case, so just get a distinct, safe string. - $id = substr(Crypt::hashBase64($url->getPath()), 0, 16); - } - else { - $id = str_replace(array('.', '<', '>'), array('-', '', ''), $url->getRouteName()); - } - - // Get the non-localized title to make the icon class. - $definition = $link->getPluginDefinition(); - - $element->options['attributes']['id'] = 'toolbar-link-' . $id; - $element->options['attributes']['class'][] = 'toolbar-icon'; - // @todo Change to use the plugin ID as class since titles might change. - // https://www.drupal.org/node/2310365 - $element->options['attributes']['class'][] = 'toolbar-icon-' . strtolower(str_replace(' ', '-', $definition['title'])); - $element->options['attributes']['title'] = String::checkPlain($link->getDescription()); + // and behaviors to the items. + $tree[$key]['link']['localized_options']['attributes'] = array( + 'id' => 'toolbar-link-' . str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['link_path']), + 'class' => array( + 'toolbar-icon', + 'toolbar-icon-' . strtolower(str_replace(' ', '-', $item['link']['link_title'])), + ), + 'title' => String::checkPlain($item['link']['description']), + ); } - return $tree; } /** * Returns the rendered subtree of each top-level toolbar link. */ function toolbar_get_rendered_subtrees() { - $menu_tree = \Drupal::menuTree(); - $parameters = new MenuTreeParameters(); - $parameters->setRoot('system.admin')->excludeRoot()->setMaxDepth(3)->excludeHiddenLinks(); - $tree = $menu_tree->load(NULL, $parameters); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - array('callable' => 'toolbar_menu_navigation_links'), - ); - $tree = $menu_tree->transform($tree, $manipulators); $subtrees = array(); - foreach ($tree as $element) { - /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ - $link = $element->link; - if ($element->subtree) { - $subtree = $menu_tree->build($element->subtree); - $output = drupal_render($subtree); - } - else { - $output = ''; - } - // Many routes have dots as route name, while some special ones like <front> - // have <> characters in them. - $id = str_replace(array('.', '<', '>'), array('-', '', '' ), $link->getUrlObject()->getRouteName()); + /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu_link.tree'); + $tree = toolbar_get_menu_tree(); + foreach ($tree as $tree_item) { + $item = $tree_item['link']; + if (!$item['hidden'] && $item['access']) { + if ($item['has_children']) { + $query = \Drupal::entityQuery('menu_link') + ->condition('has_children', 1); + for ($i=1; $i <= $item['depth']; $i++) { + $query->condition('p' . $i, $item['p' . $i]); + } + $parents = $query->execute(); + $subtree = $menu_tree->buildTree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1)); + toolbar_menu_navigation_links($subtree); + $subtree = $menu_tree->renderTree($subtree); + $subtree = drupal_render($subtree); + } + else { + $subtree = ''; + } - $subtrees[$id] = $output; + $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['link_path']); + $subtrees[$id] = $subtree; + } } return $subtrees; } @@ -525,7 +523,7 @@ function _toolbar_get_subtrees_hash($langcode) { // caches later, based on the user's ID regardless of language. // Clear the cache when the 'locale' tag is deleted. This ensures a fresh // subtrees rendering when string translations are made. - \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, array('user' => array($uid), 'locale' => TRUE, 'menu' => 'admin')); + \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, array('user' => array($uid), 'locale' => TRUE,)); } return $hash; } @@ -544,6 +542,15 @@ function toolbar_modules_uninstalled($modules) { _toolbar_clear_user_cache(); } +/** + * Implements hook_ENTITY_TYPE_update() for menu_link entities. + */ +function toolbar_menu_link_update(MenuLinkInterface $menu_link) { + if ($menu_link->menu_name === 'admin') { + _toolbar_clear_user_cache(); + } +} + /** * Implements hook_ENTITY_TYPE_update() for user entities. */ diff --git a/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php b/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php deleted file mode 100644 index 91768cdfceac..000000000000 --- a/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\user\Plugin\Menu\MyAccountMenuLink. - */ - -namespace Drupal\user\Plugin\Menu; - -use Drupal\Core\Menu\MenuLinkDefault; - -/** - * Provides custom logic for the user.page menu link. - */ -class MyAccountMenuLink extends MenuLinkDefault { - - /** - * {@inheritdoc} - */ - public function isHidden() { - // The path 'user' must be accessible for anonymous users, but only visible - // for authenticated users. Authenticated users should see "My account", but - // anonymous users should not see it at all. - // @todo Re-write this as a link to user.view with dynamic route parameters - // to affect access since hidden should not be dynamic. - // https://www.drupal.org/node/2306991 - return $this->pluginDefinition['hidden'] || \Drupal::currentUser()->isAnonymous(); - } - - /** - * {@inheritdoc} - */ - public function isCacheable() { - return FALSE; - } - -} diff --git a/core/modules/user/src/Tests/UserAccountLinksTests.php b/core/modules/user/src/Tests/UserAccountLinksTests.php index e60212019501..d57e39b291f1 100644 --- a/core/modules/user/src/Tests/UserAccountLinksTests.php +++ b/core/modules/user/src/Tests/UserAccountLinksTests.php @@ -7,7 +7,6 @@ namespace Drupal\user\Tests; -use Drupal\Core\Menu\MenuTreeParameters; use Drupal\simpletest\WebTestBase; /** @@ -44,14 +43,14 @@ function testSecondaryMenu() { // For a logged-in user, expect the secondary menu to have links for "My // account" and "Log out". $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array( - ':menu_class' => 'menu', + ':menu_class' => 'links', ':href' => 'user', ':text' => 'My account', )); $this->assertEqual(count($link), 1, 'My account link is in secondary menu.'); $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array( - ':menu_class' => 'menu', + ':menu_class' => 'links', ':href' => 'user/logout', ':text' => 'Log out', )); @@ -62,15 +61,13 @@ function testSecondaryMenu() { $this->drupalGet('<front>'); // For a logged-out user, expect no secondary links. - $menu_tree = \Drupal::menuTree(); - $tree = $menu_tree->load('account', new MenuTreeParameters()); - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - ); - $tree = $menu_tree->transform($tree, $manipulators); + /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu_link.tree'); + $tree = $menu_tree->buildTree('account'); $this->assertEqual(count($tree), 1, 'The secondary links menu contains only one menu link.'); - $element = reset($tree); - $this->assertTrue($element->link->isHidden(), 'The menu link is hidden.'); + $link = reset($tree); + $link = $link['link']; + $this->assertTrue((bool) $link->hidden, 'The menu link is hidden.'); } /** @@ -83,7 +80,7 @@ function testDisabledAccountLink() { // Verify that the 'My account' link exists before we check for its // disappearance. $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array( - ':menu_class' => 'menu', + ':menu_class' => 'links', ':href' => 'user', ':text' => 'My account', )); @@ -97,7 +94,10 @@ function testDisabledAccountLink() { $this->assertFieldChecked((string) $label[0], "The 'My account' link is enabled by default."); // Disable the 'My account' link. - $edit['links[menu_plugin_id:user.page][enabled]'] = FALSE; + $input = $this->xpath('//input[@id=:field_id]/@name', array(':field_id' => (string)$label[0])); + $edit = array( + (string) $input[0] => FALSE, + ); $this->drupalPostForm('admin/structure/menu/manage/account', $edit, t('Save')); // Get the homepage. @@ -105,7 +105,7 @@ function testDisabledAccountLink() { // Verify that the 'My account' link does not appear when disabled. $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array( - ':menu_class' => 'menu', + ':menu_class' => 'links', ':href' => 'user', ':text' => 'My account', )); diff --git a/core/modules/user/user.links.menu.yml b/core/modules/user/user.links.menu.yml index 3490a8d83d03..bc421f0a2c7c 100644 --- a/core/modules/user/user.links.menu.yml +++ b/core/modules/user/user.links.menu.yml @@ -3,7 +3,6 @@ user.page: weight: -10 route_name: user.page menu_name: account - class: Drupal\user\Plugin\Menu\MyAccountMenuLink user.logout: title: 'Log out' route_name: user.logout diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 0b3d45311d79..2253d1ccd115 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -633,6 +633,19 @@ function template_preprocess_username(&$variables) { } } +/** + * Implements hook_menu_link_presave(). + */ +function user_menu_link_presave(MenuLink $menu_link) { + // The path 'user' must be accessible for anonymous users, but only visible + // for authenticated users. Authenticated users should see "My account", but + // anonymous users should not see it at all. Therefore, invoke + // user_menu_link_load() to conditionally hide the link. + if ($menu_link->machine_name == 'user.page') { + $menu_link->options['alter'] = TRUE; + } +} + /** * Implements hook_menu_breadcrumb_alter(). */ @@ -644,6 +657,16 @@ function user_menu_breadcrumb_alter(&$active_trail, $item) { } } +/** + * Implements hook_translated_menu_link_alter(). + */ +function user_translated_menu_link_alter(MenuLink &$menu_link) { + // Hide the "User account" link for anonymous users. + if ($menu_link->machine_name == 'user.page' && \Drupal::currentUser()->isAnonymous()) { + $menu_link->hidden = 1; + } +} + /** * Finalizes the login process and logs in a user. * diff --git a/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php b/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php deleted file mode 100644 index e83af2be214e..000000000000 --- a/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\views\Plugin\Derivative\ViewsMenuLink. - */ - -namespace Drupal\views\Plugin\Derivative; - -use Drupal\Component\Plugin\Derivative\DeriverInterface; -use Drupal\views\Views; - -/** - * Provides menu links for Views. - * - * @see \Drupal\views\Plugin\Menu\ViewsMenuLink - */ -class ViewsMenuLink implements DeriverInterface { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinition($derivative_id, $base_plugin_definition) { - if (!isset($this->derivatives)) { - $this->getDerivativeDefinitions($base_plugin_definition); - } - if (isset($this->derivatives[$derivative_id])) { - return $this->derivatives[$derivative_id]; - } - } - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($base_plugin_definition) { - $links = array(); - // @todo Replace uses_hook_menu with an annotation. - // https://www.drupal.org/node/2310371 - $views = Views::getApplicableViews('uses_hook_menu'); - foreach ($views as $data) { - /** @var \Drupal\views\ViewExecutable $view */ - list($view, $display_id) = $data; - if ($result = $view->getMenuLinks($display_id)) { - foreach ($result as $link_id => $link) { - $links[$link_id] = $link + $base_plugin_definition; - } - } - } - - return $links; - } - -} diff --git a/core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php b/core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php deleted file mode 100644 index d97b204ff5b5..000000000000 --- a/core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\views\Plugin\Menu\Form\ViewsMenuLinkForm. - */ - -namespace Drupal\views\Plugin\Menu\Form; - -use Drupal\Core\Menu\Form\MenuLinkDefaultForm; - -/** - * Provides a form to edit Views menu links. - * - * This provides the feature to edit the title and description, in contrast to - * the default menu link form. - * - * @see \Drupal\views\Plugin\Menu\ViewsMenuLink - */ -class ViewsMenuLinkForm extends MenuLinkDefaultForm { - - /** - * The edited views menu link. - * - * @var \Drupal\views\Plugin\Menu\ViewsMenuLink - */ - protected $menuLink; - - /** - * {@inheritdoc} - */ - public function buildConfigurationForm(array $form, array &$form_state) { - - // Put the title field first. - $form['title'] = array( - '#type' => 'textfield', - '#title' => $this->t('Title'), - // @todo Ensure that the view is not loaded with a localized title. - // https://www.drupal.org/node/2309507 - '#default_value' => $this->menuLink->getTitle(), - '#weight' => -10, - ); - - $form['description'] = array( - '#type' => 'textfield', - '#title' => $this->t('Description'), - '#description' => $this->t('Shown when hovering over the menu link.'), - // @todo Ensure that the view is not loaded with a localized description. - // https://www.drupal.org/node/2309507 - '#default_value' => $this->menuLink->getDescription(), - '#weight' => -5, - ); - - $form += parent::buildConfigurationForm($form, $form_state); - - $form['info']['#weight'] = -8; - $form['path']['#weight'] = -7; - - $view = $this->menuLink->loadView(); - $id = $view->storage->id(); - $label = $view->storage->label(); - if ($this->moduleHandler->moduleExists('views_ui')) { - $message = $this->t('This link is provided by the Views module. The path can be changed by editing the view <a href="@url">@label</a>', array('@url' => \Drupal::url('views_ui.edit', array('view' => $id)), '@label' => $label)); - } - else { - $message = $this->t('This link is provided by the Views module from view %label.', array('%label' => $label)); - } - $form['info']['#title'] = $message; - return $form; - } - - /** - * {@inheritdoc} - */ - public function extractFormValues(array &$form, array &$form_state) { - $definition = parent::extractFormValues($form, $form_state); - $definition['title'] = $form_state['values']['title']; - $definition['description'] = $form_state['values']['description']; - - return $definition; - } - -} diff --git a/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php b/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php deleted file mode 100644 index 4e164d739173..000000000000 --- a/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php +++ /dev/null @@ -1,157 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\views\Plugin\Menu\ViewsMenuLink. - */ - -namespace Drupal\views\Plugin\Menu; - -use Drupal\Core\Menu\MenuLinkBase; -use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\views\ViewExecutableFactory; -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Defines menu links provided by views. - * - * @see \Drupal\views\Plugin\Derivative\ViewsMenuLink - */ -class ViewsMenuLink extends MenuLinkBase implements ContainerFactoryPluginInterface { - - /** - * {@inheritdoc} - */ - protected $overrideAllowed = array( - 'menu_name' => 1, - 'parent' => 1, - 'weight' => 1, - 'expanded' => 1, - 'hidden' => 1, - 'title' => 1, - 'description' => 1, - 'metadata' => 1, - ); - - /** - * The entity manager. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - - /** - * The view executable factory. - * - * @var \Drupal\views\ViewExecutableFactory - */ - protected $viewExecutableFactory; - - /** - * The view executable of the menu link. - * - * @var \Drupal\views\ViewExecutable - */ - protected $view; - - /** - * Constructs a new ViewsMenuLink. - * - * @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\Entity\EntityManagerInterface $entity_manager - * The entity manager - * @param \Drupal\views\ViewExecutableFactory $view_executable_factory - * The view executable factory - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ViewExecutableFactory $view_executable_factory) { - $this->configuration = $configuration; - $this->pluginId = $plugin_id; - $this->pluginDefinition = $plugin_definition; - - $this->entityManager = $entity_manager; - $this->viewExecutableFactory = $view_executable_factory; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity.manager'), - $container->get('views.executable') - ); - } - - /** - * Initializes the proper view. - * - * @return \Drupal\views\ViewExecutable - * The view executable. - */ - public function loadView() { - if (empty($this->view)) { - $metadata = $this->getMetaData(); - $view_id = $metadata['view_id']; - $display_id = $metadata['display_id']; - $view_entity = $this->entityManager->getStorage('view')->load($view_id); - $view = $this->viewExecutableFactory->get($view_entity); - $view->setDisplay($display_id); - $view->initDisplay(); - $this->view = $view; - } - return $this->view; - } - - /** - * {@inheritdoc} - */ - public function getTitle() { - // @todo Get the translated value from the config without instantiating the - // view. https://www.drupal.org/node/2310379 - return $this->loadView()->display_handler->getOption('menu')['title']; - } - - /** - * {@inheritdoc} - */ - public function getDescription() { - return $this->loadView()->display_handler->getOption('menu')['description']; - } - - /** - * {@inheritdoc} - */ - public function updateLink(array $new_definition_values, $persist) { - $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed); - if ($persist) { - $view = $this->loadView(); - $display = &$view->storage->getDisplay($view->current_display); - // Just save the title to the original view. - $changed = FALSE; - foreach (array('title' => 'title', 'weight' => 'weight', 'menu' => 'name', 'description' => 'description') as $definition_key => $views_key) { - if ($display['display_options']['menu'][$views_key] != $new_definition_values[$definition_key]) { - $display['display_options']['menu'][$views_key] = $new_definition_values[$definition_key]; - $changed = TRUE; - } - } - if ($changed) { - // @todo Improve this to not trigger a full rebuild of everything, if we - // just changed some properties. https://www.drupal.org/node/2310389 - $view->storage->save(); - } - } - // Update the definition. - $this->pluginDefinition = $overrides + $this->pluginDefinition; - return $this->pluginDefinition; - } - -} diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index 8507f9065758..a5e6e1566e40 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -2119,14 +2119,17 @@ public function renderMoreLink() { } /** - * Gets menu links, if this display provides some. + * Creates menu links, if this display provides some. + * + * @param array $existing_links + * An array of already existing menu items provided by drupal. * * @return array * The menu links registers for this display. * - * @see \Drupal\views\Plugin\Derivative\ViewsMenuLink + * @see hook_menu_link_defaults() */ - public function getMenuLinks() { + public function executeHookMenuLinkDefaults(array &$existing_links) { return array(); } diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php index 60f41ae6167b..45756785a5e9 100644 --- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php @@ -280,7 +280,7 @@ public function alterRoutes(RouteCollection $collection) { /** * {@inheritdoc} */ - public function getMenuLinks() { + public function executeHookMenuLinkDefaults(array &$existing_links) { $links = array(); // Replace % with the link to our standard views argument loader @@ -300,10 +300,7 @@ public function getMenuLinks() { $view_route_names = $this->state->get('views.view_route_names') ?: array(); $path = implode('/', $bits); - $view_id = $this->view->storage->id(); - $display_id = $this->display['id']; - $view_id_display = "{$view_id}.{$display_id}"; - $menu_link_id = 'views.' . str_replace('/', '.', $view_id_display); + $menu_link_id = 'views.' . str_replace('/', '.', $path); if ($path) { $menu = $this->getOption('menu'); @@ -311,11 +308,12 @@ public function getMenuLinks() { $links[$menu_link_id] = array(); // Some views might override existing paths, so we have to set the route // name based upon the altering. + $view_id_display = "{$this->view->storage->id()}.{$this->display['id']}"; $links[$menu_link_id] = array( 'route_name' => isset($view_route_names[$view_id_display]) ? $view_route_names[$view_id_display] : "view.$view_id_display", // Identify URL embedded arguments and correlate them to a handler. 'load arguments' => array($this->view->storage->id(), $this->display['id'], '%index'), - 'id' => $menu_link_id, + 'machine_name' => $menu_link_id, ); $links[$menu_link_id]['title'] = $menu['title']; $links[$menu_link_id]['description'] = $menu['description']; @@ -326,11 +324,6 @@ public function getMenuLinks() { // Insert item into the proper menu. $links[$menu_link_id]['menu_name'] = $menu['name']; - // Keep track of where we came from. - $links[$menu_link_id]['metadata'] = array( - 'view_id' => $view_id, - 'display_id' => $display_id, - ); } } diff --git a/core/modules/views/src/Tests/Plugin/DisplayPageTest.php b/core/modules/views/src/Tests/Plugin/DisplayPageTest.php index 97e5cb96a39e..0067ce3aa966 100644 --- a/core/modules/views/src/Tests/Plugin/DisplayPageTest.php +++ b/core/modules/views/src/Tests/Plugin/DisplayPageTest.php @@ -7,7 +7,6 @@ namespace Drupal\views\Tests\Plugin; -use Drupal\Core\Session\AnonymousUserSession; use Drupal\views\Views; use Drupal\views\Tests\ViewUnitTestBase; use Symfony\Component\HttpFoundation\Request; @@ -33,7 +32,7 @@ class DisplayPageTest extends ViewUnitTestBase { * * @var array */ - public static $modules = array('system', 'user', 'field', 'entity'); + public static $modules = array('system', 'user', 'menu_link', 'field', 'entity'); /** * The router dumper to get all routes. @@ -50,13 +49,13 @@ protected function setUp() { // Setup the needed tables in order to make the drupal router working. $this->installSchema('system', array('url_alias')); + $this->installSchema('menu_link', 'menu_links'); } /** * Checks the behavior of the page for access denied/not found behaviors. */ public function testPageResponses() { - \Drupal::currentUser()->setAccount(new AnonymousUserSession()); $subrequest = Request::create('/test_page_display_403', 'GET'); $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST); $this->assertEqual($response->getStatusCode(), 403); diff --git a/core/modules/views/src/Tests/Wizard/MenuTest.php b/core/modules/views/src/Tests/Wizard/MenuTest.php index a245f578718e..3e9b8be40886 100644 --- a/core/modules/views/src/Tests/Wizard/MenuTest.php +++ b/core/modules/views/src/Tests/Wizard/MenuTest.php @@ -7,8 +7,6 @@ namespace Drupal\views\Tests\Wizard; -use Drupal\Component\Utility\String; - /** * Tests the ability of the views wizard to put views in a menu. * @@ -41,14 +39,15 @@ function testMenus() { $this->assertLinkByHref(url($view['page[path]'])); // Make sure the link is associated with the main menu. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ - $link = $menu_link_manager->createInstance('views_view:views.' . $view['id'] . '.page_1'); - $url = $link->getUrlObject(); - $this->assertEqual($url->getRouteName(), 'view.' . $view['id'] . '.page_1', String::format('Found a link to %path in the main menu', array('%path' => $view['page[path]']))); - $metadata = $link->getMetaData(); - $this->assertEqual(array('view_id' => $view['id'], 'display_id' => 'page_1'), $metadata); + $links = menu_load_links('main'); + $found = FALSE; + foreach ($links as $link) { + if ($link['link_path'] == $view['page[path]']) { + $found = TRUE; + break; + } + } + $this->assertTrue($found, t('Found a link to %path in the main menu', array('%path' => $view['page[path]']))); } } diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index c7e810b769e4..ad8e801560ca 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -1509,16 +1509,17 @@ public function attachDisplays() { } /** - * Returns menu links from the view and the named display handler. + * Returns default menu links from the view and the named display handler. * * @param string $display_id * A display ID. + * @param array $links + * An array of default menu link items passed from + * views_menu_link_defaults_alter(). * * @return array|bool - * The generated menu links for this view and display, FALSE if the call - * to ::setDisplay failed. */ - public function getMenuLinks($display_id = NULL) { + public function executeHookMenuLinkDefaults($display_id = NULL, &$links = array()) { // Prepare the view with the information we have. This was probably already // called, but it's good to be safe. if (!$this->setDisplay($display_id)) { @@ -1527,7 +1528,7 @@ public function getMenuLinks($display_id = NULL) { // Execute the hook. if (isset($this->display_handler)) { - return $this->display_handler->getMenuLinks(); + return $this->display_handler->executeHookMenuLinkDefaults($links); } } diff --git a/core/modules/views/views.links.menu.yml b/core/modules/views/views.links.menu.yml deleted file mode 100644 index 61886771a3d4..000000000000 --- a/core/modules/views/views.links.menu.yml +++ /dev/null @@ -1,4 +0,0 @@ -views_view: - class: Drupal\views\Plugin\Menu\ViewsMenuLink - form_class: Drupal\views\Plugin\Menu\Form\ViewsMenuLinkForm - deriver: \Drupal\views\Plugin\Derivative\ViewsMenuLink diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 7ad98f59a64d..d9d980501d48 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -304,6 +304,23 @@ function views_permission() { ); } +/** + * Implements hook_menu_link_defaults_alter(). + */ +function views_menu_link_defaults_alter(array &$links) { + // @todo Decide what to do with all the crazy logic in views_menu_alter() in + // https://drupal.org/node/2107533. + $views = Views::getApplicableViews('uses_hook_menu'); + foreach ($views as $data) { + /** @var \Drupal\views\ViewExecutable $view */ + list($view, $display_id) = $data; + $result = $view->executeHookMenuLinkDefaults($display_id, $links); + foreach ($result as $link_id => $link) { + $links[$link_id] = $link; + } + } +} + /** * Implements hook_page_alter(). */ diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml index a1c07b23815b..70297392c11c 100644 --- a/core/profiles/standard/standard.info.yml +++ b/core/profiles/standard/standard.info.yml @@ -14,7 +14,6 @@ dependencies: - comment - contextual - contact - - menu_link_content - datetime - block_content - quickedit diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install index 673ddf7e2e43..f588cd3e7dec 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -43,11 +43,16 @@ function standard_install() { ->fields(array('uid' => 1, 'rid' => 'administrator')) ->execute(); - // Enable the Contact link in the footer menu. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $menu_link_manager->updateDefinition('contact.site_page', array('hidden' => 0)); + // Create a Home link in the main menu. + $menu_link = entity_create('menu_link', array( + 'link_title' => t('Home'), + 'link_path' => '<front>', + 'menu_name' => 'main', + )); + $menu_link->save(); + // Enable the Contact link in the footer menu. + menu_link_maintain('contact', 'enable', 'contact'); user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access site-wide contact form')); user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access site-wide contact form')); diff --git a/core/profiles/standard/standard.links.menu.yml b/core/profiles/standard/standard.links.menu.yml deleted file mode 100644 index 2278b2163b9b..000000000000 --- a/core/profiles/standard/standard.links.menu.yml +++ /dev/null @@ -1,4 +0,0 @@ -standard.front_page: - title: 'Home' - route_name: '<front>' - menu_name: main diff --git a/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php b/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php index 9a9e98e4bc2d..7086aa6025d9 100644 --- a/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php +++ b/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php @@ -28,6 +28,7 @@ class MenuLinkMock extends MenuLinkBase { 'options' => array(), 'expanded' => '0', 'hidden' => '0', + 'discovered' => '1', 'provider' => 'simpletest', 'metadata' => array(), 'class' => 'Drupal\\Tests\\Core\Menu\\MenuLinkMock', diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme index 27f8973c4613..836a8f72df4a 100644 --- a/core/themes/bartik/bartik.theme +++ b/core/themes/bartik/bartik.theme @@ -55,15 +55,18 @@ function bartik_preprocess_page(&$variables) { // Store back the classes to the htmlpage object. $attributes['class'] = $classes; - // Set additional attributes on the primary and secondary navigation menus. + // Pass the main menu and secondary menu to the template as render arrays. if (!empty($variables['main_menu'])) { $variables['main_menu']['#attributes']['id'] = 'main-menu-links'; - $variables['main_menu']['#attributes']['class'][] = 'links'; + $variables['main_menu']['#attributes']['class'] = array('links', 'clearfix'); } if (!empty($variables['secondary_menu'])) { $variables['secondary_menu']['#attributes']['id'] = 'secondary-menu-links'; - $variables['secondary_menu']['#attributes']['class'][] = 'links'; - $variables['secondary_menu']['#attributes']['class'][] = 'inline'; + $variables['secondary_menu']['#attributes']['class'] = array( + 'links', + 'inline', + 'clearfix', + ); } // Set the options that apply to both page and maintenance page. @@ -135,12 +138,10 @@ function bartik_preprocess_block(&$variables) { } /** - * Implements hook_preprocess_HOOK() for menu-tree.html.twig. - * - * @see template_preprocess_menu_tree() + * Implements THEME_menu_tree(). */ -function bartik_preprocess_menu_tree(&$variables) { - $variables['attributes']['class'][] = 'clearfix'; +function bartik_menu_tree($variables) { + return '<ul class="menu clearfix">' . $variables['tree'] . '</ul>'; } /** diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index 0ff1bdd4da76..8cdc9c1725e1 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -145,7 +145,7 @@ function seven_preprocess_block_content_add_list(&$variables) { function seven_preprocess_admin_block_content(&$variables) { if (!empty($variables['content'])) { foreach ($variables['content'] as $key => $item) { - $variables['content'][$key]['url'] = $item['url']->toString(); + $variables['content'][$key]['url'] = url($item['link_path']); } } } -- GitLab