diff --git a/core/core.services.yml b/core/core.services.yml index 0bb13d0..a70aa6b 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -63,6 +63,13 @@ services: factory_method: get factory_service: cache_factory arguments: [entity] + cache.menu: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin } + factory_method: get + factory_service: cache_factory + arguments: [menu] cache.render: class: Drupal\Core\Cache\CacheBackendInterface tags: @@ -265,6 +272,21 @@ services: plugin.manager.action: class: Drupal\Core\Action\ActionManager arguments: ['@container.namespaces', '@cache.discovery', '@module_handler'] + plugin.manager.menu.link: + class: Drupal\Core\Menu\MenuLinkManager + arguments: ['@menu.tree_storage', '@menu_link.static.overrides', '@module_handler'] + menu.link_tree: + class: Drupal\Core\Menu\MenuLinkTree + 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'] + menu.active_trail: + class: Drupal\Core\Menu\MenuActiveTrail + arguments: ['@plugin.manager.menu.link', '@current_route_match'] + menu.parent_form_selector: + class: Drupal\Core\Menu\MenuParentFormSelector + arguments: ['@menu.link_tree', '@entity.manager'] plugin.manager.menu.local_action: class: Drupal\Core\Menu\LocalActionManager arguments: ['@controller_resolver', '@request_stack', '@router.route_provider', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user'] @@ -279,6 +301,18 @@ services: parent: default_plugin_manager plugin.cache_clearer: class: Drupal\Core\Plugin\CachedDiscoveryClearer + paramconverter.menu_link: + class: Drupal\Core\ParamConverter\MenuLinkPluginConverter + tags: + - { name: paramconverter } + arguments: ['@plugin.manager.menu.link'] + menu.tree_storage: + class: Drupal\Core\Menu\MenuTreeStorage + arguments: ['@database', '@cache.menu', 'menu_tree'] + public: false # Private to plugin.manager.menu.link and menu.link_tree + menu_link.static.overrides: + class: Drupal\Core\Menu\StaticMenuLinkOverrides + arguments: ['@config.factory'] request: class: Symfony\Component\HttpFoundation\Request synthetic: true diff --git a/core/includes/menu.inc b/core/includes/menu.inc index ae35c07..e4398ce 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -264,30 +264,31 @@ /** * The maximum depth of a menu links tree - matches the number of p columns. * - * @todo Move this constant to MenuLinkStorage along with all the tree - * functionality. - */ -const MENU_MAX_DEPTH = 9; - - -/** - * @} End of "defgroup menu_tree_parameters". - */ - -/** - * Reserved key to identify the most specific menu link for a given path. - * - * 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 - * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91 - * @endcode + * @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. * - * @see menu_link_get_preferred() + * 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\menu_link\MenuTree::build(), this will build + * a renderable array. */ -const MENU_PREFERRED_LINK = '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91'; - + /** * Localizes a menu link title using t() if possible. * @@ -378,21 +379,39 @@ function _menu_link_translate(&$item) { * Implements template_preprocess_HOOK() for theme_menu_tree(). */ function template_preprocess_menu_tree(&$variables) { - $variables['tree'] = $variables['tree']['#children']; -} + if (isset($variables['tree']['#heading'])) { + $variables['heading'] = $variables['tree']['#heading']; + $heading = &$variables['heading']; + // Convert a string heading into an array, using a H2 tag by default. + if (is_string($heading)) { + $heading = array('text' => $heading); + } + // Merge in default array properties into $heading. + $heading += array( + 'level' => 'h2', + 'attributes' => array(), + ); + // @todo Remove backwards compatibility for $heading['class']. + 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']); + } -/** - * 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 ''; + 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']; } /** @@ -411,8 +430,10 @@ function theme_menu_link(array $variables) { if ($element['#below']) { $sub_menu = drupal_render($element['#below']); } - $element['#localized_options']['set_active_class'] = TRUE; - $output = l($element['#title'], $element['#href'], $element['#localized_options']); + /** @var \Drupal\Core\Url $url */ + $url = $element['#url']; + $url->setOption('set_active_class', TRUE); + $output = \Drupal::linkGenerator()->generateFromUrl($element['#title'], $url); return '' . $output . $sub_menu . "\n"; } @@ -552,56 +573,29 @@ function _menu_get_links_source($name, $default) { } /** - * Returns an array of links for a navigation menu. + * Builds a renderable array for a navigation menu. * - * @param $menu_name + * @param string $menu_name * The name of the menu. - * @param $level + * @param int $level * Optional, the depth of the menu to be returned. * - * @return - * An array of links of the specified menu and level. + * @return array + * A renderable array. */ function menu_navigation_links($menu_name, $level = 0) { - // 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; + /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu.link_tree'); + $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); } /** @@ -864,8 +858,7 @@ 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('data')->deleteAll(); - menu_reset_static_cache(); + \Drupal::cache('menu')->invalidateAll(); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 81c328c..c5376be 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2113,27 +2113,17 @@ 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'] = 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, + $variables['main_menu']['#heading'] = array( + 'text' => t('Main menu'), + 'class' => array('visually-hidden'), + 'attributes' => array('id' => 'links__system_main_menu'), ); } if (!empty($variables['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, + $variables['secondary_menu']['#heading'] = array( + 'text' => t('Secondary menu'), + 'class' => array('visually-hidden'), + 'attributes' => array('id' => 'links__system_secondary_menu'), ); } @@ -2619,6 +2609,7 @@ 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 6409fd3..4684a00 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -647,4 +647,14 @@ 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 d5a6334..e4d4639 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php @@ -255,10 +255,7 @@ protected function getTableMapping($table, $entity_type_id) { $mapping = $storage->getTableMapping()->getAllColumns($table); } else { - // @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 FALSE; } return array_flip($mapping); } diff --git a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php index 87fdb87..f492af7 100644 --- a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php @@ -76,8 +76,6 @@ protected function menuLinksRebuild() { 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(); diff --git a/core/lib/Drupal/Core/ParamConverter/MenuLinkPluginConverter.php b/core/lib/Drupal/Core/ParamConverter/MenuLinkPluginConverter.php new file mode 100644 index 0000000..b3c2dcf --- /dev/null +++ b/core/lib/Drupal/Core/ParamConverter/MenuLinkPluginConverter.php @@ -0,0 +1,58 @@ +menuLinkManager = $menu_link_manager; + } + + /** + * {@inheritdoc} + */ + public function convert($value, $definition, $name, array $defaults, Request $request) { + if ($value) { + try { + return $this->menuLinkManager->createInstance($value); + } + catch (PluginException $e) { + // Suppress the error. + } + } + } + + /** + * {@inheritdoc} + */ + public function applies($definition, $name, Route $route) { + return (!empty($definition['type']) && $definition['type'] === 'menu_link_plugin'); + } + +} diff --git a/core/lib/Drupal/Core/Plugin/CachedDiscoveryClearer.php b/core/lib/Drupal/Core/Plugin/CachedDiscoveryClearer.php index 5febcd3..9ab429f 100644 --- a/core/lib/Drupal/Core/Plugin/CachedDiscoveryClearer.php +++ b/core/lib/Drupal/Core/Plugin/CachedDiscoveryClearer.php @@ -21,7 +21,7 @@ class CachedDiscoveryClearer { * * @var \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface[] */ - protected $cachedDiscoveries; + protected $cachedDiscoveries = array(); /** * Adds a plugin manager to the active list. diff --git a/core/lib/Drupal/Core/Plugin/PluginManagerPass.php b/core/lib/Drupal/Core/Plugin/PluginManagerPass.php index 8bfde35..9efdcf2 100644 --- a/core/lib/Drupal/Core/Plugin/PluginManagerPass.php +++ b/core/lib/Drupal/Core/Plugin/PluginManagerPass.php @@ -23,7 +23,9 @@ public function process(ContainerBuilder $container) { $cache_clearer_definition = $container->getDefinition('plugin.cache_clearer'); foreach ($container->getDefinitions() as $service_id => $definition) { if (strpos($service_id, 'plugin.manager.') === 0 || $definition->hasTag('plugin_manager_cache_clear')) { - $cache_clearer_definition->addMethodCall('addCachedDiscovery', array(new Reference($service_id))); + if (is_subclass_of($definition->getClass(), '\Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface')) { + $cache_clearer_definition->addMethodCall('addCachedDiscovery', array(new Reference($service_id))); + } } } } diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc index 4379582..f13b403 100644 --- a/core/modules/book/book.admin.inc +++ b/core/modules/book/book.admin.inc @@ -5,6 +5,7 @@ * Administration page callbacks for the Book module. */ +use Drupal\book\BookManager; use Drupal\Core\Render\Element; /** @@ -86,7 +87,7 @@ function theme_book_admin_table($variables) { 'subgroup' => 'book-pid', 'source' => 'book-nid', 'hidden' => TRUE, - 'limit' => MENU_MAX_DEPTH - 2, + 'limit' => BookManager::BOOK_MAX_DEPTH - 2, ), array( 'action' => 'order', diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 54aad06..aa85719 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -5,6 +5,7 @@ * 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; @@ -123,7 +124,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'] < MENU_MAX_DEPTH) { + 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) { $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 0d22898..a738ddb 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_link_defaults_alter(). + * Implements hook_menu_links_discovered_alter(). */ -function content_translation_menu_link_defaults_alter(array &$links) { +function content_translation_menu_links_discovered_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 0db6520..c83a837 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php @@ -50,7 +50,7 @@ function setUp() { * Tests that the settings UI works as expected. */ function testSettingsUI() { - // Check for the content_translation_menu_link_defaults_alter() changes. + // Check for the content_translation_menu_links_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 25a4b0e..50b09e1 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_link_defaults_alter(). + * Implements hook_menu_links_discovered_alter(). */ -function dblog_menu_link_defaults_alter(&$links) { +function dblog_menu_links_discovered_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 5aa7929..40c1eb7 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_link_defaults_alter(). + * Implements hook_menu_links_discovered_alter(). * * Rewrites the menu entries for filter module that relate to the configuration * of text editors. */ -function editor_menu_link_defaults_alter(array &$links) { +function editor_menu_links_discovered_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 f0db942..9067f38 100644 --- a/core/modules/entity/src/Controller/EntityDisplayModeController.php +++ b/core/modules/entity/src/Controller/EntityDisplayModeController.php @@ -8,6 +8,7 @@ namespace Drupal\entity\Controller; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Url; /** * Provides methods for entity display mode routes. @@ -26,7 +27,7 @@ public function viewModeTypeSelection() { if ($entity_type->isFieldable() && $entity_type->hasViewBuilderClass()) { $entity_types[$entity_type_id] = array( 'title' => $entity_type->getLabel(), - 'link_path' => 'admin/structure/display-modes/view/add/' . $entity_type_id, + 'url' => Url::createFromPath('admin/structure/display-modes/view/add/' . $entity_type_id), 'localized_options' => array(), ); } @@ -49,7 +50,7 @@ public function formModeTypeSelection() { if ($entity_type->isFieldable() && $entity_type->hasFormClasses()) { $entity_types[$entity_type_id] = array( 'title' => $entity_type->getLabel(), - 'link_path' => 'admin/structure/display-modes/form/add/' . $entity_type_id, + 'url' => Url::createFromPath('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 26a3160..437c3a8 100644 --- a/core/modules/help/src/Controller/HelpController.php +++ b/core/modules/help/src/Controller/HelpController.php @@ -125,8 +125,7 @@ public function helpPage($name) { if (!empty($admin_tasks)) { $links = array(); foreach ($admin_tasks as $task) { - $link = $task['localized_options']; - $link['href'] = $task['link_path']; + $link = $task['url']->toArray(); $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 35e207a..6a2e96f 100644 --- a/core/modules/language/src/Tests/LanguageConfigSchemaTest.php +++ b/core/modules/language/src/Tests/LanguageConfigSchemaTest.php @@ -22,7 +22,7 @@ class LanguageConfigSchemaTest extends WebTestBase { * * @var array */ - public static $modules = array('language'); + public static $modules = array('language', 'menu_link_content'); /** * A user with administrative permissions. @@ -61,8 +61,8 @@ function testValidLanguageConfigSchema() { $settings_path = 'admin/config/regional/content-language'; // Enable translation for menu link. - $edit['entity_types[menu_link]'] = TRUE; - $edit['settings[menu_link][menu_link][settings][language][language_show]'] = TRUE; + $edit['entity_types[menu_link_content]'] = TRUE; + $edit['settings[menu_link_content][menu_link_content][settings][language][language_show]'] = TRUE; // Enable translation for user. $edit['entity_types[user]'] = TRUE; @@ -73,7 +73,7 @@ function testValidLanguageConfigSchema() { $config_data = \Drupal::config('language.settings')->get(); // Make sure configuration saved correctly. - $this->assertTrue($config_data['entities']['menu_link']['menu_link']['language']['default_configuration']['language_show']); + $this->assertTrue($config_data['entities']['menu_link_content']['menu_link_content']['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 7c27253..3913b7e 100644 --- a/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php +++ b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php @@ -20,7 +20,7 @@ class LocaleLocaleLookupTest extends WebTestBase { * * @var array */ - public static $modules = array('locale', 'menu_link'); + public static $modules = array('locale'); /** * {@inheritdoc} diff --git a/core/modules/menu_ui/menu_ui.admin.inc b/core/modules/menu_ui/menu_ui.admin.inc index 0d7a0fd..b8f0ef6 100644 --- a/core/modules/menu_ui/menu_ui.admin.inc +++ b/core/modules/menu_ui/menu_ui.admin.inc @@ -5,15 +5,19 @@ * Administrative page callbacks for Menu UI module. */ +use Drupal\Core\Menu\MenuLinkTreeInterface; use Drupal\Core\Render\Element; /** * Returns HTML for the menu overview form into a table. * - * @param $variables + * @param array $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) { @@ -27,27 +31,28 @@ function theme_menu_overview_form($variables) { ); $rows = array(); - foreach (Element::children($form) as $mlid) { - if (isset($form[$mlid]['hidden'])) { - $element = &$form[$mlid]; + foreach (Element::children($form) as $id) { + if (isset($form[$id]['#item'])) { + $element = &$form[$id]; // Add special classes to be used for tabledrag.js. - $element['plid']['#attributes']['class'] = array('menu-plid'); - $element['mlid']['#attributes']['class'] = array('menu-mlid'); + $element['parent']['#attributes']['class'] = array('menu-parent'); + $element['id']['#attributes']['class'] = array('menu-id'); $element['weight']['#attributes']['class'] = array('menu-weight'); - // Change the parent field to a hidden. This allows any value but hides the field. - $element['plid']['#type'] = 'hidden'; + // Change the parent field to a hidden. This allows any value but hides + // the field. + $element['parent']['#type'] = 'hidden'; $indent = array( '#theme' => 'indentation', - '#size' => $element['#item']['depth'] - 1, + '#size' => $element['#item']->depth - 1, ); $row = array(); $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[] = array('data' => drupal_render($element['enabled']), 'class' => array('checkbox', 'menu-enabled')); + $row[] = drupal_render($element['weight']) . drupal_render($element['parent']) . drupal_render($element['id']); $row[] = drupal_render($element['operations']); $row = array_merge(array('data' => $row), $element['#attributes']); @@ -71,11 +76,11 @@ function theme_menu_overview_form($variables) { array( 'action' => 'match', 'relationship' => 'parent', - 'group' => 'menu-plid', - 'subgroup' => 'menu-plid', - 'source' => 'menu-mlid', + 'group' => 'menu-parent', + 'subgroup' => 'menu-parent', + 'source' => 'menu-id', 'hidden' => TRUE, - 'limit' => MENU_MAX_DEPTH - 1, + 'limit' => \Drupal::service('menu.link_tree')->maxDepth() - 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 26611f1..9dd90da 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 + - menu_link_content \ No newline at end of file diff --git a/core/modules/menu_ui/menu_ui.local_actions.yml b/core/modules/menu_ui/menu_ui.local_actions.yml index 0834821..af8716f 100644 --- a/core/modules/menu_ui/menu_ui.local_actions.yml +++ b/core/modules/menu_ui/menu_ui.local_actions.yml @@ -1,6 +1,7 @@ -menu_ui_link_add: - route_name: menu_ui.link_add +menu_ui.link_add: + route_name: menu_link_content.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 5e2f5bc..13596e3 100644 --- a/core/modules/menu_ui/menu_ui.module +++ b/core/modules/menu_ui/menu_ui.module @@ -11,15 +11,13 @@ * URLs to be added to the main site navigation menu. */ -use Drupal\Core\Entity\EntityInterface; use Drupal\block\BlockPluginInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Menu\MenuLinkInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; 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; /** @@ -75,31 +73,11 @@ 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.link_add') + ->setLinkTemplate('add-form', 'menu_ui.menu_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(). @@ -122,23 +100,6 @@ 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(); - } } /** @@ -157,20 +118,9 @@ function menu_ui_menu_update(Menu $menu) { */ function menu_ui_menu_predelete(Menu $menu) { // Delete all links from the menu. - 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(); - } + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); + $menu_link_manager->deleteLinksInMenu($menu->id()); } /** @@ -186,113 +136,18 @@ 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'. */ function menu_ui_block_view_system_menu_block_alter(array &$build, BlockPluginInterface $block) { // Add contextual links for system menu blocks. $menus = menu_list_system_menus(); $menu_name = $block->getDerivativeId(); - if (isset($menus[$menu_name])) { - $build['#contextual_links']['menu'] = array( - 'route_parameters' => array('menu' => $menu_name), - ); + if (isset($menus[$menu_name]) && isset($build['content'])) { + foreach (Element::children($build['content']) as $key) { + $build['#contextual_links']['menu'] = array( + 'route_parameters' => array('menu' => $menu_name), + ); + } } } @@ -310,7 +165,7 @@ function menu_ui_node_update(EntityInterface $node) { menu_ui_node_save($node); } -/** + /** * Implements hook_node_type_insert(). */ function menu_ui_node_type_insert(NodeTypeInterface $type) { @@ -319,7 +174,7 @@ function menu_ui_node_type_insert(NodeTypeInterface $type) { } \Drupal::config('menu.entity.node.' . $type->id()) ->set('available_menus', array('main')) - ->set('parent', 'main:0') + ->set('parent', 'main:') ->save(); } @@ -333,29 +188,40 @@ function menu_ui_node_type_delete(NodeTypeInterface $type) { \Drupal::config('menu.entity.node.' . $type->id())->delete(); } + /** * Helper for hook_node_insert() and hook_node_update(). */ function menu_ui_node_save(EntityInterface $node) { - 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']); + if (!empty($node->menu)) { + /** @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface $link */ + $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; } else { - // If the description field was left empty, remove the title attribute - // from the menu link. - unset($link['options']['attributes']['title']); + // 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 (!menu_link_save($link)) { + if (!$entity->save()) { drupal_set_message(t('There was an error saving the menu link.'), 'error'); } } @@ -366,14 +232,17 @@ function menu_ui_node_save(EntityInterface $node) { * Implements hook_node_predelete(). */ function menu_ui_node_predelete(EntityInterface $node) { - // 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(); + // 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())); if (!empty($result)) { - menu_link_delete_multiple($result); + foreach ($result as $id => $instance) { + if ($instance->isDeletable() && strpos($id, 'menu_link_content:') === 0) { + $instance->deleteLink(); + } + } } } @@ -381,67 +250,69 @@ 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($node->menu)) { + if (empty($form_state['menu_link'])) { // 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'), ':'); - $menu_link = FALSE; + $definition = FALSE; if ($node->id()) { - $mlid = FALSE; + $id = 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') - ->condition('link_path', 'node/' . $node->id()) + $query = \Drupal::entityQuery('menu_link_content') + ->condition('route_name', 'node.view') + ->condition('route_parameters', serialize(array('node' => $node->id()))) ->condition('menu_name', $menu_name) - ->condition('module', 'menu_ui') - ->sort('mlid', 'ASC') + ->sort('id', 'ASC') ->range(0, 1); $result = $query->execute(); - $mlid = (!empty($result)) ? reset($result) : FALSE; + $id = (!empty($result)) ? reset($result) : FALSE; } // Check all allowed menus if a link does not exist in the default menu. - if (!$mlid && !empty($type_menus)) { - $query = \Drupal::entityQuery('menu_link') - ->condition('link_path', 'node/' . $node->id()) + if (!$id && !empty($type_menus)) { + $query = \Drupal::entityQuery('menu_link_content') + ->condition('route_name', 'node.view') + ->condition('route_parameters', serialize(array('node' => $node->id()))) ->condition('menu_name', array_values($type_menus), 'IN') - ->condition('module', 'menu_ui') - ->sort('mlid', 'ASC') + ->sort('id', 'ASC') ->range(0, 1); $result = $query->execute(); - $mlid = (!empty($result)) ? reset($result) : FALSE; + $id = (!empty($result)) ? reset($result) : FALSE; } - if ($mlid) { - $menu_link = menu_link_load($mlid); + if ($id) { + $menu_link = \Drupal::entityManager()->getStorage('menu_link_content')->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 (!$menu_link) { - $menu_link = entity_create('menu_link', array( - 'mlid' => 0, - 'plid' => 0, + if (!$definition) { + $definition = array( + 'entity_id' => 0, + 'id' => '', + 'title' => '', + 'description' => '', 'menu_name' => $menu_name, - )); + 'parent' => '', + 'weight' => 0, + ); } // Set default values. - $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); + $form_state['menu_link'] = $definition; } } /** - * 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(). * * Adds menu item fields to the node form. @@ -452,11 +323,25 @@ 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(); - $link = $node->menu; + $definition = $form_state['menu_link']; $type = $node->getType(); - $options = menu_ui_parent_options(menu_ui_get_menus(), $link, $type); + /** @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); // If no possible parent menu items were found, there is nothing to display. - if (empty($options)) { + if (empty($parent_element)) { return; } @@ -464,7 +349,7 @@ function menu_ui_form_node_form_alter(&$form, $form_state) { '#type' => 'details', '#title' => t('Menu settings'), '#access' => \Drupal::currentUser()->hasPermission('administer menu'), - '#open' => !empty($link['link_title']), + '#open' => (bool) $definition['id'], '#group' => 'advanced', '#attached' => array( 'library' => array('menu/drupal.menu'), @@ -476,7 +361,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) $link['mlid'], + '#default_value' => (int) (bool) $definition['id'], ); $form['menu']['link'] = array( '#type' => 'container', @@ -489,57 +374,32 @@ function menu_ui_form_node_form_alter(&$form, $form_state) { ); // Populate the element with the link data. - 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]); + foreach (array('id', 'entity_id') as $key) { + $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $definition[$key]); } - $form['menu']['link']['link_title'] = array( + $form['menu']['link']['title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), - '#default_value' => $link['link_title'], + '#default_value' => $definition['title'], ); $form['menu']['link']['description'] = array( '#type' => 'textarea', '#title' => t('Description'), - '#default_value' => isset($link['options']['attributes']['title']) ? $link['options']['attributes']['title'] : '', + '#default_value' => $definition['description'], '#rows' => 1, '#description' => t('Shown when hovering over the menu link.'), ); - 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')), - ); + $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'; - // 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' => 'weight', + '#type' => 'number', '#title' => t('Weight'), - '#delta' => $delta, - '#default_value' => $link['weight'], + '#default_value' => $definition['weight'], '#description' => t('Menu links with lower weights are displayed before links with higher weights.'), ); } @@ -551,18 +411,24 @@ 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'])) { - $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); + $definition = $form_state['values']['menu']; + if (empty($definition['enabled'])) { + if ($definition['entity_id']) { + $entity = entity_load('menu_link_content', $definition['entity_id']); + $entity->delete(); + } } - // 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']); + 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; + } + // Have to tack this onto the node so we can save it later when we have a + // a node ID for any new node. + $node->menu = $definition; } } } @@ -576,15 +442,18 @@ 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()) { $config_values = \Drupal::config('menu.entity.node.' . $type->id())->get(); + //drupal_set_message(print_r($config_values,1)); } else { $config_values = array( 'available_menus' => array('main'), - 'parent' => 'main:0', + 'parent' => 'main:', ); } $form['menu'] = array( @@ -605,9 +474,8 @@ function menu_ui_form_node_type_form_alter(&$form, $form_state) { // 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); + // @todo Convert getParentSelectOptions() into a #process callback. + $options = $menu_parent_selector->getParentSelectOptions(''); $form['menu']['menu_parent'] = array( '#type' => 'select', '#title' => t('Default parent item'), @@ -639,7 +507,8 @@ 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 + * + * @return array * An array with the machine-readable names as the keys, and human-readable * titles as the values. */ @@ -664,3 +533,20 @@ 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 4be3bbf..319d922 100644 --- a/core/modules/menu_ui/menu_ui.routing.yml +++ b/core/modules/menu_ui/menu_ui.routing.yml @@ -21,37 +21,30 @@ 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/item/{menu_link}/edit' + path: '/admin/structure/menu/link/{menu_link_plugin}/edit' defaults: - _entity_form: 'menu_link' + _form: 'Drupal\menu_ui\Form\MenuLinkEditForm' _title: 'Edit menu link' + options: + parameters: + menu_link_plugin: + type: menu_link_plugin requirements: - _entity_access: 'menu_link.update' + _permission: 'administer menu' menu_ui.link_reset: - path: '/admin/structure/menu/item/{menu_link}/reset' + path: '/admin/structure/menu/link/{menu_link_plugin}/reset' defaults: - _entity_form: 'menu_link.reset' + _form: 'Drupal\menu_ui\Form\MenuLinkResetForm' _title: 'Reset menu link' + options: + parameters: + menu_link_plugin: + type: menu_link_plugin requirements: - _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' + _permission: 'administer menu' + _custom_access: '\Drupal\menu_ui\Form\MenuLinkResetForm::linkIsResetable' 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 fad5de5..f8b1580 100644 --- a/core/modules/menu_ui/src/Controller/MenuController.php +++ b/core/modules/menu_ui/src/Controller/MenuController.php @@ -34,30 +34,14 @@ public function getParentOptions(Request $request) { $available_menus[$menu] = $menu; } } - $options = _menu_ui_get_options(menu_ui_get_menus(), $available_menus, array('mlid' => 0)); + /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */ + $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); + $options = $menu_parent_selector->getParentSelectOptions('', $available_menus); 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. * * @param \Drupal\system\MenuInterface $menu diff --git a/core/modules/menu_ui/src/Form/MenuDeleteForm.php b/core/modules/menu_ui/src/Form/MenuDeleteForm.php index d6ceff4..a4ca8ec 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\Entity\EntityStorageInterface; +use Drupal\Core\Menu\MenuLinkManagerInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -19,11 +19,11 @@ class MenuDeleteForm extends EntityConfirmFormBase { /** - * The menu link storage. + * The menu link manager. * - * @var \Drupal\Core\Entity\EntityStorageInterface + * @var \Drupal\Core\Menu\MenuLinkManagerInterface */ - protected $storage; + protected $menuLinkManager; /** * The database connection. @@ -35,13 +35,13 @@ class MenuDeleteForm extends EntityConfirmFormBase { /** * Constructs a new MenuDeleteForm. * - * @param \Drupal\Core\Entity\EntityStorageInterface $storage - * The menu link storage. + * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager + * The menu link manager. * @param \Drupal\Core\Database\Connection $connection * The database connection. */ - public function __construct(EntityStorageInterface $storage, Connection $connection) { - $this->storage = $storage; + public function __construct(MenuLinkManagerInterface $menu_link_manager, Connection $connection) { + $this->menuLinkManager = $menu_link_manager; $this->connection = $connection; } @@ -50,7 +50,7 @@ public function __construct(EntityStorageInterface $storage, Connection $connect */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager')->getStorage('menu_link'), + $container->get('plugin.manager.menu.link'), $container->get('database') ); } @@ -74,7 +74,7 @@ public function getCancelRoute() { */ public function getDescription() { $caption = ''; - $num_links = $this->storage->countMenuLinks($this->entity->id()); + $num_links = $this->menuLinkManager->countMenuLinks($this->entity->id()); if ($num_links) { $caption .= '

' . format_plural($num_links, 'Warning: There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', 'Warning: There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $this->entity->label())) . '

'; } @@ -100,21 +100,12 @@ public function submit(array $form, array &$form_state) { return; } - // 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)); + // @todo - there ought to be a better way. + $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_ui.menu_edit', array('menu' => $this->entity->id()), TRUE); + foreach ($menu_links as $id => $link) { + $this->menuLinkManager->deleteLink($id); + } // Delete the custom menu and all its menu links. $this->entity->delete(); diff --git a/core/modules/menu_ui/src/Form/MenuLinkEditForm.php b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php new file mode 100644 index 0000000..5207e0f --- /dev/null +++ b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php @@ -0,0 +1,102 @@ +classResolver = $class_resolver; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('class_resolver') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'menu_link_edit'; + } + + /** + * {@inheritdoc} + * + * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin + * The plugin instance to use for this form. + */ + public function buildForm(array $form, array &$form_state, MenuLinkInterface $menu_link_plugin = NULL) { + + $form['menu_link_id'] = array( + '#type' => 'value', + '#value' => $menu_link_plugin->getPluginId(), + ); + $class_name = $menu_link_plugin->getFormClass(); + $form['#plugin_form'] = $this->classResolver->getInstanceFromDefinition($class_name); + $form['#plugin_form']->setMenuLinkInstance($menu_link_plugin); + + $form += $form['#plugin_form']->buildConfigurationForm($form, $form_state); + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#button_type' => 'primary', + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + $form['#plugin_form']->validateConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + $link = $form['#plugin_form']->submitConfigurationForm($form, $form_state); + + drupal_set_message($this->t('The menu link has been saved.')); + $form_state['redirect_route'] = array( + 'route_name' => 'menu_ui.menu_edit', + 'route_parameters' => array( + 'menu' => $link->getMenuName(), + ), + ); + } + +} diff --git a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php index d3d2a67..b86b682 100644 --- a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php +++ b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php @@ -7,19 +7,63 @@ 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 EntityConfirmFormBase { +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 MenuLinkEditForm 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'; + } /** * {@inheritdoc} */ public function getQuestion() { - return t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->entity->link_title)); + return $this->t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->link->getTitle())); } /** @@ -27,7 +71,7 @@ public function getQuestion() { */ public function getCancelRoute() { return new Url('menu_ui.menu_edit', array( - 'menu' => $this->entity->menu_name, + 'menu' => $this->link->getMenuName(), )); } @@ -35,23 +79,47 @@ public function getCancelRoute() { * {@inheritdoc} */ public function getDescription() { - return t('Any customizations will be lost. This action cannot be undone.'); + return $this->t('Any customizations will be lost. This action cannot be undone.'); } /** * {@inheritdoc} */ public function getConfirmText() { - return t('Reset'); + return $this->t('Reset'); } /** * {@inheritdoc} */ - 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.')); + 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.')); $form_state['redirect_route'] = $this->getCancelRoute(); } + /** + * 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 + * Returns AccessInterface::ALLOW when access was granted, otherwise + * AccessInterface::DENY. + */ + public function linkIsResetable(MenuLinkInterface $menu_link_plugin) { + return $menu_link_plugin->isResetable() ? AccessInterface::ALLOW : AccessInterface::DENY; + } + } diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php index d854f3c..183c992 100644 --- a/core/modules/menu_ui/src/MenuForm.php +++ b/core/modules/menu_ui/src/MenuForm.php @@ -11,9 +11,11 @@ use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Menu\MenuLinkTreeElement; +use Drupal\Core\Menu\MenuLinkTreeInterface; +use Drupal\Core\Menu\MenuLinkManagerInterface; +use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Render\Element; -use Drupal\menu_link\MenuLinkStorageInterface; -use Drupal\menu_link\MenuTreeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -29,16 +31,16 @@ class MenuForm extends EntityForm { protected $entityQueryFactory; /** - * The menu link storage. + * The menu link manager. * - * @var \Drupal\menu_link\MenuLinkStorageInterface + * @var \Drupal\Core\Menu\MenuLinkManagerInterface */ - protected $menuLinkStorage; + protected $menuLinkManager; /** * The menu tree service. * - * @var \Drupal\menu_link\MenuTreeInterface + * @var \Drupal\Core\Menu\MenuLinkTreeInterface */ protected $menuTree; @@ -54,14 +56,14 @@ class MenuForm extends EntityForm { * * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory * The factory for entity queries. - * @param \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage - * The menu link storage. - * @param \Drupal\menu_link\MenuTreeInterface $menu_tree + * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager + * The menu link manager. + * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree * The menu tree service. */ - public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageInterface $menu_link_storage, MenuTreeInterface $menu_tree) { + public function __construct(QueryFactory $entity_query_factory, MenuLinkManagerInterface $menu_link_manager, MenuLinkTreeInterface $menu_tree) { $this->entityQueryFactory = $entity_query_factory; - $this->menuLinkStorage = $menu_link_storage; + $this->menuLinkManager = $menu_link_manager; $this->menuTree = $menu_tree; } @@ -71,8 +73,8 @@ public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageI public static function create(ContainerInterface $container) { return new static( $container->get('entity.query'), - $container->get('entity.manager')->getStorage('menu_link'), - $container->get('menu_link.tree') + $container->get('plugin.manager.menu.link'), + $container->get('menu.link_tree') ); } @@ -88,16 +90,16 @@ public function form(array $form, array &$form_state) { $form['label'] = array( '#type' => 'textfield', - '#title' => t('Title'), + '#title' => $this->t('Title'), '#default_value' => $menu->label(), '#required' => TRUE, ); $form['id'] = array( '#type' => 'machine_name', - '#title' => t('Menu name'), + '#title' => $this->t('Menu name'), '#default_value' => $menu->id(), '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI, - '#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'), + '#description' => $this->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'), @@ -120,28 +122,11 @@ 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 to handle the form section's submission + // within forms, but does not allow handling 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() @@ -169,41 +154,7 @@ public function menuNameExists($value) { } // Check for a link assigned to this menu. - 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(); + return $this->menuLinkManager->menuNameInUse($value); } /** @@ -219,11 +170,11 @@ public function save(array $form, array &$form_state) { $edit_link = \Drupal::linkGenerator()->generateFromUrl($this->t('Edit'), $this->entity->urlInfo()); if ($status == SAVED_UPDATED) { - drupal_set_message(t('Menu %label has been updated.', array('%label' => $menu->label()))); + drupal_set_message($this->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(t('Menu %label has been added.', array('%label' => $menu->label()))); + drupal_set_message($this->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); } @@ -253,26 +204,28 @@ protected function buildOverviewForm(array &$form, array &$form_state) { $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/css/menu.admin.css'); - $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); - } + $tree = $this->menuTree->load($this->entity->id(), new MenuTreeParameters()); - $delta = max(count($links), 50); // We indicate that a menu administrator is running the menu access check. + $manipulators = array( + array('callable' => 'menu.default_tree_manipulators:checkAccess'), + array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), + ); $this->getRequest()->attributes->set('_menu_admin', TRUE); - $tree = $this->menuTree->buildTreeData($links); + $tree = $this->menuTree->transform($tree, $manipulators); $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)); - $form['#empty_text'] = t('There are no menu links yet. Add link.', array('@link' => url('admin/structure/menu/manage/' . $this->entity->id() .'/add'))); + $form['#empty_text'] = $this->t('There are no menu links yet. Add link.', array('@link' => url('admin/structure/menu/manage/' . $this->entity->id() .'/add'))); return $form; } @@ -290,70 +243,87 @@ protected function buildOverviewForm(array &$form, array &$form_state) { */ protected function buildOverviewTreeForm($tree, $delta) { $form = &$this->overviewTreeForm; - 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') . ')'; + 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'] = \Drupal::linkGenerator()->generateFromUrl($link->getTitle(), $link->getUrlObject(), $link->getOptions()); + if ($link->isHidden()) { + $form[$id]['title']['#markup'] .= ' (' . $this->t('disabled') . ')'; } - elseif ($item['link_path'] == 'user' && $item['module'] == 'user') { - $form[$mlid]['title']['#markup'] .= ' (' . t('logged in users only') . ')'; + elseif (($url = $link->getUrlObject()) && !$url->isExternal() && $url->getRouteName() == 'user.page') { + $form[$id]['title']['#markup'] .= ' (' . $this->t('logged in users only') . ')'; } - $form[$mlid]['hidden'] = array( + $form[$id]['enabled'] = array( '#type' => 'checkbox', - '#title' => t('Enable @title menu link', array('@title' => $item['title'])), + '#title' => $this->t('Enable @title menu link', array('@title' => $link->getTitle())), '#title_display' => 'invisible', - '#default_value' => !$item['hidden'], + '#default_value' => !$link->isHidden(), ); - $form[$mlid]['weight'] = array( + $form[$id]['weight'] = array( '#type' => 'weight', '#delta' => $delta, - '#default_value' => $item['weight'], - '#title' => t('Weight for @title', array('@title' => $item['title'])), + '#default_value' => $link->getWeight(), + '#title' => $this->t('Weight for @title', array('@title' => $link->getTitle())), '#title_display' => 'invisible', ); - $form[$mlid]['mlid'] = array( + $form[$id]['id'] = array( '#type' => 'hidden', - '#value' => $item['mlid'], + '#value' => $link->getPluginId(), ); - $form[$mlid]['plid'] = array( + $form[$id]['parent'] = array( '#type' => 'hidden', - '#default_value' => $item['plid'], + '#default_value' => $link->getParent(), ); // Build a list of operations. $operations = array(); $operations['edit'] = array( - 'title' => t('Edit'), - 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/edit', + 'title' => $this->t('Edit'), ); - // 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', + // 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()), ); } - // Set the reset column. - elseif ($item->access('reset')) { + // Links can either be reset or deleted, not both. + if ($link->isResetable()) { $operations['reset'] = array( - 'title' => t('Reset'), - 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/reset', + 'title' => $this->t('Reset'), + 'route_name' => 'menu_ui.link_reset', + 'route_parameters' => array('menu_link_plugin' => $link->getPluginId()), ); } - $form[$mlid]['operations'] = array( + 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( '#type' => 'operations', '#links' => $operations, ); } - if ($data['below']) { - $this->buildOverviewTreeForm($data['below'], $delta); + if ($element->subtree) { + $this->buildOverviewTreeForm($element->subtree, $delta); } } return $form; @@ -363,7 +333,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 menu tree. + * underneath them. Saving items in the incorrect order can break the tree. */ protected function submitOverviewForm(array $complete_form, array &$form_state) { // Form API supports constructing and validating self-contained sections @@ -384,32 +354,30 @@ 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); - $updated_items = array(); - $fields = array('weight', 'plid'); - foreach (Element::children($form) as $mlid) { - if (isset($form[$mlid]['#item'])) { - $element = $form[$mlid]; + $fields = array('weight', 'parent', 'enabled'); + foreach (Element::children($form) as $id) { + if (isset($form[$id]['#item'])) { + $element = $form[$id]; + $updated_values = array(); // Update any fields that have changed in this menu item. foreach ($fields as $field) { if ($element[$field]['#value'] != $element[$field]['#default_value']) { - $element['#item'][$field] = $element[$field]['#value']; - $updated_items[$mlid] = $element['#item']; + // 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']; + } } } - // 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']; + if ($updated_values) { + // Use the ID from the actual plugin instance since the hidden value + // in the form could be tampered with. + $this->menuLinkManager->updateLink($element['#item']->link->getPLuginId(), $updated_values); } } } - - // 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 new file mode 100644 index 0000000..88e1898 --- /dev/null +++ b/core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php @@ -0,0 +1,32 @@ +attributes->has('_system_path')) { + // @todo: is there a better value to get from the request? + $options['query']['destination'] = $request->attributes->get('_system_path'); + } + return $options; + } + +} diff --git a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php index 8c73348..96aa9c4 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\Tests\MenuCacheTagsTest. + * Contains \Drupal\menu_ui\Tests\MenuCacheTagsTest. */ namespace Drupal\menu_ui\Tests; @@ -46,12 +46,10 @@ public function testMenuBlock() { 'description' => 'Description text', )); $menu->save(); - $menu_link = entity_create('menu_link', array( - 'link_path' => '', - 'link_title' => 'Vicuña', - 'menu_name' => 'llama', - )); - $menu_link->save(); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('plugin.manager.menu.link'); + // Move a link into the new menu. + $menu_link = $menu_link_manager->updateLink('test_page_test.test_page', array('menu_name' => 'llama', 'parent' => '')); $block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'provider' => 'system', 'region' => 'footer')); // Prime the page cache. @@ -69,7 +67,6 @@ 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'; @@ -79,23 +76,24 @@ public function testMenuBlock() { // Verify a cache hit. $this->verifyPageCache($path, 'HIT'); - - // Verify that after modifying the menu link, there is a cache miss. + // Verify that after modifying the menu link weight, there is a cache miss. + $menu_link_manager->updateLink('test_page_test.test_page', array('weight' => -10)); $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', array( - 'link_path' => '', - 'link_title' => 'Alpaca', + $menu_link_2 = entity_create('menu_link_content', array( + 'id' => '', + 'parent' => '', + 'title' => 'Alpaca', 'menu_name' => 'llama', + 'route_name' => '', + 'bundle' => 'menu_name', )); $menu_link_2->save(); $this->verifyPageCache($path, 'MISS'); @@ -103,16 +101,15 @@ public function testMenuBlock() { // Verify a cache hit. $this->verifyPageCache($path, 'HIT'); - - // Verify that after deleting the first menu link, there is a cache miss. - $this->pass('Test deletion of menu link.', 'Debug'); - $menu_link->delete(); + // 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->isResetable(), 'First link can be reset'); + $menu_link = $menu_link_manager->resetLink($menu_link->getPluginId()); $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 8d9890e..6588874 100644 --- a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php +++ b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php @@ -64,21 +64,12 @@ 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')); - - // 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); + language_save_default_configuration('menu_link_content', 'menu_link_content', array('langcode' => 'bb', '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 = ''; @@ -86,41 +77,35 @@ function testMenuLanguage() { // Add a menu link. $link_title = $this->randomString(); $edit = array( - 'link_title' => $link_title, - 'link_path' => $link_path, + 'title[0][value]' => $link_title, + 'url' => $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', array('link_title' => $link_title)); + $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title)); $menu_link = reset($menu_links); - $this->assertMenuLink($menu_link->id(), array( + $this->assertMenuLink($menu_link->getPluginId(), array( 'menu_name' => $menu_name, - 'link_path' => $link_path, + 'route_name' => '', 'langcode' => 'bb', )); // Edit menu link default, changing it to cc. - $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.'); + language_save_default_configuration('menu_link_content', 'menu_link_content', array('langcode' => 'cc', 'language_show' => TRUE)); // Add a menu link. $link_title = $this->randomString(); $edit = array( - 'link_title' => $link_title, - 'link_path' => $link_path, + 'title[0][value]' => $link_title, + 'url' => $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', array('link_title' => $link_title)); + $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title)); $menu_link = reset($menu_links); - $this->assertMenuLink($menu_link->id(), array( + $this->assertMenuLink($menu_link->getPluginId(), array( 'menu_name' => $menu_name, - 'link_path' => $link_path, + 'route_name' => '', 'langcode' => 'cc', )); @@ -129,9 +114,9 @@ function testMenuLanguage() { 'langcode' => 'bb', ); $this->drupalPostForm('admin/structure/menu/item/' . $menu_link->id() . '/edit', $edit, t('Save')); - $this->assertMenuLink($menu_link->id(), array( + $this->assertMenuLink($menu_link->getPluginId(), array( 'menu_name' => $menu_name, - 'link_path' => $link_path, + 'route_name' => '', 'langcode' => 'bb', )); @@ -143,16 +128,7 @@ 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. - $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); + language_save_default_configuration('menu_link_content', 'menu_link_content', array('langcode' => 'cc', '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 9c1c67e..2fcfaea 100644 --- a/core/modules/menu_ui/src/Tests/MenuNodeTest.php +++ b/core/modules/menu_ui/src/Tests/MenuNodeTest.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\menu_ui\Tests\MenuNodeTest. + * Contains \Drupal\menu_ui\Tests\MenuNodeTest. */ namespace Drupal\menu_ui\Tests; @@ -71,7 +71,7 @@ function testMenuNodeFormWidget() { $edit = array( 'menu_options[main]' => 1, 'menu_options[tools]' => 1, - 'menu_parent' => 'main:0', + 'menu_parent' => 'main:', ); $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type')); @@ -99,7 +99,7 @@ function testMenuNodeFormWidget() { // Edit the node and create a menu link. $edit = array( 'menu[enabled]' => 1, - 'menu[link_title]' => $node_title, + 'menu[title]' => $node_title, 'menu[weight]' => 17, ); $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); @@ -108,7 +108,7 @@ function testMenuNodeFormWidget() { $this->assertLink($node_title); $this->drupalGet('node/' . $node->id() . '/edit'); - $this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form'); + $this->assertFieldById('edit-menu-weight', 17, 'Menu weight correct in edit form'); // Edit the node and remove the menu link. $edit = array( @@ -120,10 +120,12 @@ function testMenuNodeFormWidget() { $this->assertNoLink($node_title); // Add a menu link to the Administration menu. - $item = entity_create('menu_link', array( - 'link_path' => 'node/' . $node->id(), - 'link_title' => $this->randomName(16), + $item = entity_create('menu_link_content', array( + 'route_name' => 'node.view', + 'route_parameters' => array('node' => $node->id()), + 'title' => $this->randomName(16), 'menu_name' => 'admin', + 'bundle' => 'menu_link_content', )); $item->save(); @@ -133,27 +135,30 @@ 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 = menu_link_load($item['mlid']); + $link = entity_load('menu_link_content', $item->id()); $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'] = 'tools'; - menu_link_save($item); + $item->menu_name->value = 'tools'; + $item->save(); // 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', array( - 'link_path' => 'node/'. $child_node->id(), - 'link_title' => $this->randomName(16), - 'plid' => $item['mlid'], + $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->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-parent', 'tools:'. $item['mlid']); - $this->assertNoOption('edit-menu-parent', 'tools:'. $child_item['mlid']); + $this->assertNoOption('edit-menu-menu-parent', 'tools:'. $item->getPluginId()); + $this->assertNoOption('edit-menu-menu-parent', 'tools:'. $child_item->getPluginId()); // Assert that unallowed Administration menu is not available in options. - $this->assertNoOption('edit-menu-parent', 'admin:0'); + $this->assertNoOption('edit-menu-menu-parent', 'admin:'); } } diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php index e9ce5ef..96bdbd1 100644 --- a/core/modules/menu_ui/src/Tests/MenuTest.php +++ b/core/modules/menu_ui/src/Tests/MenuTest.php @@ -2,13 +2,15 @@ /** * @file - * Definition of Drupal\menu_ui\Tests\MenuTest. + * Contains \Drupal\menu_ui\Tests\MenuTest. */ namespace Drupal\menu_ui\Tests; use Drupal\Component\Serialization\Json; use Drupal\system\Entity\Menu; +use Drupal\menu_link_content\Entity\MenuLinkContent; + /** * Defines a test class for testing menu and menu link functionality. @@ -46,7 +48,7 @@ class MenuTest extends MenuWebTestBase { /** * An array of test menu links. * - * @var array + * @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface[] */ protected $items; @@ -83,18 +85,21 @@ function testMenu() { // Verify that the menu links rebuild is idempotent and leaves the same // number of links in the table. - $before_count = db_query('SELECT COUNT(*) FROM {menu_links}')->fetchField(); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('plugin.manager.menu.link'); + $before_count = $menu_link_manager->countMenuLinks(NULL); menu_link_rebuild_defaults(); - $after_count = db_query('SELECT COUNT(*) FROM {menu_links}')->fetchField(); + $after_count = $menu_link_manager->countMenuLinks(NULL); $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(substr($item['link_path'], 5)); - $this->verifyMenuLink($item, $node); + $node = node_load($item->getRouteParameters()['node']); + $this->verifyMenuLink($item, $node, NULL, NULL, "node/{$node->id()}"); } // Login the administrator. @@ -109,20 +114,18 @@ function testMenu() { $this->deleteCustomMenu(); // Modify and reset a standard menu link. - $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); + $instance = $this->getStandardMenuLink(); + // 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.'); } /** @@ -219,7 +222,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', array('menu_name' => $menu_name)); + $result = entity_load_multiple_by_properties('menu_link_content', 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,33 +253,32 @@ function doMenuTests() { )); // Add menu links. - $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, + $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()), // 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['mlid'], array( - 'depth' => 2, 'has_children' => 1, - 'p1' => $item1['mlid'], - 'p2' => $item2['mlid'], - 'p3' => 0, + $this->assertMenuLink($item2->getPluginId(), array( + 'children' => array($item3->getPluginId()), + 'parents' => array($item2->getPluginId(), $item1->getPluginId()), // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item3['mlid'], array( - 'depth' => 3, - 'has_children' => 0, - 'p1' => $item1['mlid'], - 'p2' => $item2['mlid'], - 'p3' => $item3['mlid'], - 'p4' => 0, + $this->assertMenuLink($item3->getPluginId(), array( + 'children' => array(), + 'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item1->getPluginId()), // See above. 'langcode' => 'en', )); @@ -287,34 +289,37 @@ function doMenuTests() { $this->verifyMenuLink($item3, $node3, $item2, $node2); // Add more menu links. - $item4 = $this->addMenuLink(0, 'node/' . $node4->id(), $menu_name); - $item5 = $this->addMenuLink($item4['mlid'], 'node/' . $node5->id(), $menu_name); + $item4 = $this->addMenuLink('', 'node/' . $node4->id(), $menu_name); + $item5 = $this->addMenuLink($item4->getPluginId(), 'node/' . $node5->id(), $menu_name); // Create a menu link pointing to an alias. - $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, + $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()), // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item5['mlid'], array( - 'depth' => 2, - 'has_children' => 0, - 'p1' => $item4['mlid'], - 'p2' => $item5['mlid'], - 'p3' => 0, - // See above. + $this->assertMenuLink($item5->getPluginId(), array( + 'children' => array(), + 'parents' => array($item5->getPluginId(), $item4->getPluginId()), 'langcode' => 'en', )); - $this->assertMenuLink($item6['mlid'], array( - 'depth' => 2, - 'has_children' => 0, - 'p1' => $item4['mlid'], - 'p2' => $item6['mlid'], - 'p3' => 0, - 'link_path' => 'node/' . $node5->id(), + $this->assertMenuLink($item6->getPluginId(), array( + 'children' => array(), + 'parents' => array($item6->getPluginId(), $item4->getPluginId()), + 'route_name' => 'node.view', + 'route_parameters' => array('node' => $node5->id()), + 'url' => '', // See above. 'langcode' => 'en', )); @@ -328,50 +333,44 @@ function doMenuTests() { $this->toggleMenuLink($item2); // Move link and verify that descendants are updated. - $this->moveMenuLink($item2, $item5['mlid'], $menu_name); - $this->assertMenuLink($item1['mlid'], array( - 'depth' => 1, - 'has_children' => 0, - 'p1' => $item1['mlid'], - 'p2' => 0, + $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name); + // Hierarchy + // <$menu_name> + // - item1 (disabled) + // - item4 + // -- item5 + // --- item2 (disabled) + // ---- item3 + // -- item6 + + $this->assertMenuLink($item1->getPluginId(), array( + 'children' => array(), + 'parents' => array($item1->getPluginId()), // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item4['mlid'], array( - 'depth' => 1, - 'has_children' => 1, - 'p1' => $item4['mlid'], - 'p2' => 0, + $this->assertMenuLink($item4->getPluginId(), array( + 'children' => array($item5->getPluginId(), $item6->getPluginId(), $item2->getPluginId(), $item3->getPluginId()), + 'parents' => array($item4->getPluginId()), // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item5['mlid'], array( - 'depth' => 2, - 'has_children' => 1, - 'p1' => $item4['mlid'], - 'p2' => $item5['mlid'], - 'p3' => 0, + + $this->assertMenuLink($item5->getPluginId(), array( + 'children' => array($item2->getPluginId(), $item3->getPluginId()), + 'parents' => array($item5->getPluginId(), $item4->getPluginId()), // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item2['mlid'], array( - 'depth' => 3, - 'has_children' => 1, - 'p1' => $item4['mlid'], - 'p2' => $item5['mlid'], - 'p3' => $item2['mlid'], - 'p4' => 0, + $this->assertMenuLink($item2->getPluginId(), array( + 'children' => array($item3->getPluginId()), + 'parents' => array($item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()), // See above. 'langcode' => 'en', )); - $this->assertMenuLink($item3['mlid'], array( - 'depth' => 4, - 'has_children' => 0, - 'p1' => $item4['mlid'], - 'p2' => $item5['mlid'], - 'p3' => $item2['mlid'], - 'p4' => $item3['mlid'], - 'p5' => 0, + $this->assertMenuLink($item3->getPluginId(), array( + 'children' => array(), + 'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()), // See above. 'langcode' => 'en', )); @@ -380,33 +379,31 @@ 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(0, 'node/' . $node1->id(), $menu_name, TRUE, strval($i)); + $items[$i] = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name, TRUE, strval($i)); } - $this->assertMenuLink($items[51]['mlid'], array('weight' => '51')); + $this->assertMenuLink($items[51]->getPluginId(), array('weight' => '51')); // Enable a link via the overview form. $this->disableMenuLink($item1); $edit = array(); - // 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')); + $edit['links[menu_plugin_id:' . $item1->getPluginId() . '][enabled]'] = TRUE; + $this->drupalPostForm('admin/structure/menu/manage/' . $item1->getMenuName(), $edit, t('Save')); // Verify in the database. - $this->assertMenuLink($item1['mlid'], array('hidden' => 0)); + $this->assertMenuLink($item1->getPluginId(), array('hidden' => 0)); // Add an external link. - $item7 = $this->addMenuLink(0, 'http://drupal.org', $menu_name); - $this->assertMenuLink($item7['mlid'], array('link_path' => 'http://drupal.org', 'external' => 1)); + $item7 = $this->addMenuLink('', 'http://drupal.org', $menu_name); + $this->assertMenuLink($item7->getPluginId(), array('url' => 'http://drupal.org')); // Add menu item. - $item8 = $this->addMenuLink(0, '', $menu_name); - $this->assertMenuLink($item8['mlid'], array('link_path' => '', 'external' => 1)); + $item8 = $this->addMenuLink('', '', $menu_name); + $this->assertMenuLink($item8->getPluginId(), array('route_name' => '')); $this->drupalGet(''); $this->assertResponse(200); // Make sure we get routed correctly. - $this->clickLink($item8['link_title']); + $this->clickLink($item8->getTitle()); $this->assertResponse(200); // Save menu links for later tests. @@ -422,16 +419,16 @@ function testMenuQueryAndFragment() { // Make a path with query and fragment on. $path = 'test-page?arg1=value1&arg2=value2'; - $item = $this->addMenuLink(0, $path); + $item = $this->addMenuLink('', $path); - $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit'); - $this->assertFieldByName('link_path', $path, 'Path is found with both query and fragment.'); + $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); + $this->assertFieldByName('url', $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['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.'); + $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.'); } /** @@ -461,15 +458,15 @@ function testUnpublishedNodeMenuItem() { 'status' => NODE_NOT_PUBLISHED, )); - $item = $this->addMenuLink(0, 'node/' . $node->id()); + $item = $this->addMenuLink('', '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['menu_name']); - $this->assertNoText($item['link_title'], "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission"); + $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"); } /** @@ -495,42 +492,12 @@ public function testBlockContextualLinks() { } /** - * 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' => '')); - $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 integer $plid + * @param string $parent * Optional parent menu link id. - * @param string $link - * Link path. Defaults to the front page. + * @param string $path + * The path to enter on the form. Defaults to the front page. * @param string $menu_name * Menu name. Defaults to 'tools'. * @param bool $expanded @@ -539,40 +506,36 @@ public function testMenuBundles() { * 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\Entity\MenuLink + * @return \Drupal\menu_link_content\Entity\MenuLinkContent * A menu link entity. */ - function addMenuLink($plid = 0, $link = '', $menu_name = 'tools', $expanded = TRUE, $weight = '0', $actual_link = FALSE) { + function addMenuLink($parent = '', $path = '', $menu_name = 'tools', $expanded = FALSE, $weight = '0') { // View add menu link page. $this->drupalGet("admin/structure/menu/manage/$menu_name/add"); $this->assertResponse(200); $title = '!link_' . $this->randomName(16); $edit = array( - 'link_path' => $link, - 'link_title' => $title, - 'description' => '', - 'enabled' => TRUE, + 'url' => $path, + 'title[0][value]' => $title, + 'description[0][value]' => '', + 'enabled' => 1, 'expanded' => $expanded, - 'parent' => $menu_name . ':' . $plid, - 'weight' => $weight, + 'menu_parent' => $menu_name . ':' . $parent, + 'weight[0][value]' => $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', array('link_title' => $title)); + $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $title)); + $menu_link = reset($menu_links); $this->assertTrue($menu_link, 'Menu link was found in database.'); - $this->assertMenuLink($menu_link->id(), array('menu_name' => $menu_name, 'link_path' => $actual_link, 'has_children' => 0, 'plid' => $plid)); + $this->assertMenuLink($menu_link->getPluginId(), array('menu_name' => $menu_name, 'children' => array(), 'parent' => $parent)); return $menu_link; } @@ -583,35 +546,35 @@ function addMenuLink($plid = 0, $link = '', $menu_name = 'tools', $expand function addInvalidMenuLink() { foreach (array('-&-', 'admin/people/permissions', '#') as $link_path) { $edit = array( - 'link_path' => $link_path, - 'link_title' => 'title', + 'url' => $link_path, + 'title[0][value]' => 'title', ); $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save')); - $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'); + $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'); } } /** * Verifies a menu link using the UI. * - * @param array $item + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item * Menu link. * @param object $item_node * Menu link content node. - * @param array $parent + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent * Parent menu link. * @param object $parent_node * Parent menu link content node. */ - function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL) { + function verifyMenuLink(MenuLinkContent $item, $item_node, $parent = NULL, $parent_node = NULL, $path = '') { // View home page. - $this->drupalGet(''); + $this->drupalGet($path); $this->assertResponse(200); // Verify parent menu link. if (isset($parent)) { // Verify menu link. - $title = $parent['link_title']; + $title = $parent->getTitle(); $this->assertLink($title, 0, 'Parent menu link was displayed'); // Verify menu link link. @@ -621,7 +584,7 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL) } // Verify menu link. - $title = $item['link_title']; + $title = $item->getTitle(); $this->assertLink($title, 0, 'Menu link was displayed'); // Verify menu link link. @@ -633,18 +596,18 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL) /** * Changes the parent of a menu link using the UI. * - * @param array $item + * @param \Drupal\menu_link_content\Entity\MenuLinkContentInterface $item * The menu link item to move. - * @param int $plid + * @param int $parent * The id of the new parent. * @param string $menu_name * The menu the menu link will be moved to. */ - function moveMenuLink($item, $plid, $menu_name) { - $mlid = $item['mlid']; + function moveMenuLink(MenuLinkContent $item, $parent, $menu_name) { + $mlid = $item->id(); $edit = array( - 'parent' => $menu_name . ':' . $plid, + 'menu_parent' => $menu_name . ':' . $parent, ); $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); $this->assertResponse(200); @@ -653,58 +616,35 @@ function moveMenuLink($item, $plid, $menu_name) { /** * Modifies a menu link using the UI. * - * @param array $item - * Menu link passed by reference. + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item + * Menu link entity. */ - function modifyMenuLink(&$item) { - $item['link_title'] = $this->randomName(16); + function modifyMenuLink(MenuLinkContent $item) { + $item->title->value = $this->randomName(16); - $mlid = $item['mlid']; - $title = $item['link_title']; + $mlid = $item->id(); + $title = $item->getTitle(); // Edit menu link. $edit = array(); - $edit['link_title'] = $title; + $edit['title[0][value]'] = $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['menu_name']); + $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName()); $this->assertText($title, 'Menu link was edited'); } /** - * Resets a standard menu link using the UI. - * - * @param array $item - * Menu link. - * @param string $old_title - * Original title for menu link. - */ - function resetMenuLink($item, $old_title) { - $mlid = $item['mlid']; - $title = $item['link_title']; - - // Reset menu link. - $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. - $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 array $item + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item * Menu link. */ - function deleteMenuLink($item) { - $mlid = $item['mlid']; - $title = $item['link_title']; + function deleteMenuLink(MenuLinkContent $item) { + $mlid = $item->id(); + $title = $item->getTitle(); // Delete menu link. $this->drupalPostForm("admin/structure/menu/item/$mlid/delete", array(), t('Confirm')); @@ -719,51 +659,51 @@ function deleteMenuLink($item) { /** * Alternately disables and enables a menu link. * - * @param $item + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item * Menu link. */ - function toggleMenuLink($item) { + function toggleMenuLink(MenuLinkContent $item) { $this->disableMenuLink($item); // Verify menu link is absent. $this->drupalGet(''); - $this->assertNoText($item['link_title'], 'Menu link was not displayed'); + $this->assertNoText($item->getTitle(), 'Menu link was not displayed'); $this->enableMenuLink($item); // Verify menu link is displayed. $this->drupalGet(''); - $this->assertText($item['link_title'], 'Menu link was displayed'); + $this->assertText($item->getTitle(), 'Menu link was displayed'); } /** * Disables a menu link. * - * @param $item + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item * Menu link. */ - function disableMenuLink($item) { - $mlid = $item['mlid']; + function disableMenuLink(MenuLinkContent $item) { + $mlid = $item->id(); $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($mlid, array('hidden' => 1)); + $this->assertMenuLink($item->getPluginId(), array('hidden' => 1)); } /** * Enables a menu link. * - * @param $item + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item * Menu link. */ - function enableMenuLink($item) { - $mlid = $item['mlid']; + function enableMenuLink(MenuLinkContent $item) { + $mlid = $item->id(); $edit['enabled'] = TRUE; $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); // Verify in the database. - $this->assertMenuLink($mlid, array('hidden' => 0)); + $this->assertMenuLink($item->getPluginId(), array('hidden' => 0)); } /** @@ -787,27 +727,19 @@ public function testMenuParentsJsAccess() { /** * Returns standard menu link. * - * @return \Drupal\menu_link\Entity\MenuLink - * A menu link entity. + * @return \Drupal\Core\Menu\MenuLinkInterface + * A menu link plugin. */ private function getStandardMenuLink() { - $mlid = 0; // Retrieve menu link id of the Log out menu link, which will always be on // the front page. - $query = \Drupal::entityQuery('menu_link') - ->condition('module', 'user') - ->condition('machine_name', 'user.logout'); - $result = $query->execute(); - if (!empty($result)) { - $mlid = reset($result); - } + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('plugin.manager.menu.link'); + $result = $menu_link_manager->loadLinksByRoute('user.logout'); + $instance = reset($result); - $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; + $this->assertTrue((bool) $instance, 'Standard menu link was loaded'); + return $instance; } /** @@ -838,9 +770,9 @@ private function verifyAccess($response = 200) { $this->assertText(t('Tools'), 'Tools menu page was displayed'); } - // View menu edit page. + // View menu edit page for a static link. $item = $this->getStandardMenuLink(); - $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit'); + $this->drupalGet('admin/structure/menu/link/' . $item->getPluginId() . '/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 5f16536..9c4b627 100644 --- a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php +++ b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php @@ -19,28 +19,59 @@ * * @var array */ - public static $modules = array('menu_ui'); + public static $modules = array('menu_ui', 'menu_link_content'); /** * Fetchs the menu item from the database and compares it to expected item. * - * @param int $mlid + * @param int $menu_plugin_id * Menu item id. - * @param array $item + * @param array $expected_item * Array containing properties to verify. */ - function assertMenuLink($mlid, array $expected_item) { + function assertMenuLink($menu_plugin_id, array $expected_item) { // Retrieve menu link. - $item = entity_load('menu_link', $mlid); - $options = $item->options; - if (!empty($options['query'])) { - $item['link_path'] .= '?' . \Drupal::urlGenerator()->httpBuildQuery($options['query']); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('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); + $links = \Drupal::entityManager()->getStorage('menu_link_content')->loadByProperties(array('uuid' => $uuid)); + /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $link */ + $entity = reset($links); + } + + 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']); } - if (!empty($options['fragment'])) { - $item['link_path'] .= '#' . $options['fragment']; + + 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']); } + foreach ($expected_item as $key => $value) { - $this->assertEqual($item[$key], $value); + $this->assertTrue(isset($definition[$key])); + $this->assertEqual($definition[$key], $value); } } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 35ac533..6aba59e 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -759,10 +759,10 @@ function hook_node_validate(\Drupal\node\NodeInterface $node, $form, &$form_stat * @ingroup node_api_hooks */ function hook_node_submit(\Drupal\node\NodeInterface $node, $form, &$form_state) { - // Decompose the selected menu parent option into 'menu_name' and 'plid', if + // Decompose the selected menu parent option into 'menu_name' and 'parent', 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']); + list($node->menu['menu_name'], $node->menu['parent']) = 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 41347e0..0a17d54 100644 --- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php +++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php @@ -220,7 +220,7 @@ function testInstallConfig() { */ function testEnableModulesFixedList() { // Install system module. - $this->container->get('module_handler')->install(array('system', 'menu_link')); + $this->container->get('module_handler')->install(array('system', 'menu_link_content')); $entity_manager = \Drupal::entityManager(); // entity_test is loaded via $modules; its entity type should exist. diff --git a/core/modules/system/config/install/menu_link.static.overrides.yml b/core/modules/system/config/install/menu_link.static.overrides.yml new file mode 100644 index 0000000..ca4ba7f --- /dev/null +++ b/core/modules/system/config/install/menu_link.static.overrides.yml @@ -0,0 +1 @@ +definitions: [] diff --git a/core/modules/system/config/install/system.menu.yml b/core/modules/system/config/install/system.menu.yml deleted file mode 100644 index e73636c..0000000 --- a/core/modules/system/config/install/system.menu.yml +++ /dev/null @@ -1 +0,0 @@ -active_menus_default: [] diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 5375524..f524376 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -168,17 +168,6 @@ 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' @@ -391,3 +380,30 @@ condition.plugin.request_path: mapping: pages: type: string + +menu_link.static.overrides: + type: mapping + label: 'Menu link overrides' + mapping: + definitions: + type: sequence + label: Definitions + sequence: + - type: mapping + label: Definition + mapping: + menu_name: + type: string + label: 'Menu name' + parent: + type: string + label: 'Parent' + weight: + type: integer + label: 'Weight' + expanded: + type: boolean + label: 'Expanded' + hidden: + type: boolean + label: 'Hidden' diff --git a/core/modules/system/src/Controller/AdminController.php b/core/modules/system/src/Controller/AdminController.php index 7445d52..d9800f0 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.people.permissions.$module"; + $permission_key = "user.admin_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 b960968..459f1e6 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -12,6 +12,8 @@ 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; @@ -57,6 +59,13 @@ class SystemController extends ControllerBase { protected $themeHandler; /** + * The menu link tree service. + * + * @var \Drupal\Core\Menu\MenuLinkTreeInterface + */ + protected $menuLinkTree; + + /** * Constructs a new SystemController. * * @param \Drupal\system\SystemManager $systemManager @@ -69,13 +78,16 @@ 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) { + public function __construct(SystemManager $systemManager, QueryFactory $queryFactory, ThemeAccessCheck $theme_access, FormBuilderInterface $form_builder, ThemeHandlerInterface $theme_handler, MenuLinkTreeInterface $menu_link_tree) { $this->systemManager = $systemManager; $this->queryFactory = $queryFactory; $this->themeAccess = $theme_access; $this->formBuilder = $form_builder; $this->themeHandler = $theme_handler; + $this->menuLinkTree = $menu_link_tree; } /** @@ -87,66 +99,51 @@ 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('theme_handler'), + $container->get('menu.link_tree') ); } /** * Provide the administration overview page. * - * @param string $path - * The administrative path for which to display child links. + * @param string $link_id + * The ID of and administrative path link for which to display child links. * * @return array * A renderable array of the administration overview page. */ - public function overview($path) { + public function overview($link_id) { // 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 status report for more information.', array('@status' => url('admin/reports/status'))), 'error'); } + $parameters = new MenuTreeParameters(); + $parameters->setRoot($link_id)->excludeRoot()->topLevelOnly()->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(); - // 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), - ); + // Load all menu links below it. + 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), + ); - 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 (!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[$key] = $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 f825071..607c0f3 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -9,18 +9,19 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; +use Drupal\Core\Controller\TitleResolverInterface; 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\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Access\AccessManager; +use Symfony\Component\HttpFoundation\Request; /** * Provides module installation interface. @@ -61,11 +62,18 @@ class ModulesListForm extends FormBase { protected $entityManager; /** - * The query factory. + * The title resolver. * - * @var \Drupal\Core\Entity\Query\QueryFactory + * @var \Drupal\Core\Controller\TitleResolverInterface */ - protected $queryFactory; + protected $titleResolver; + + /** + * The route provider. + * + * @var \Drupal\Core\Routing\RouteProviderInterface + */ + protected $routeProvider; /** * The current route match. @@ -83,9 +91,10 @@ 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('current_route_match'), + $container->get('title_resolver'), + $container->get('router.route_provider') ); } @@ -100,21 +109,24 @@ 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. */ - public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManager $access_manager, EntityManagerInterface $entity_manager, QueryFactory $query_factory, AccountInterface $current_user, RouteMatchInterface $route_match) { + public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManager $access_manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, RouteMatchInterface $route_match, TitleResolverInterface $title_resolver, RouteProviderInterface $route_provider) { $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; } /** @@ -249,11 +261,13 @@ 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)) { - $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); + + $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); + $title = $this->titleResolver->getTitle($request, $route_object); + $row['links']['configure'] = array( '#type' => 'link', '#title' => $this->t('Configure'), @@ -262,7 +276,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) { '#options' => array( 'attributes' => array( 'class' => array('module-link', 'module-link-configure'), - 'title' => $item['description'], + 'title' => $title, ), ), ); diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php index 5381471..9a93288 100644 --- a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php +++ b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php @@ -10,7 +10,8 @@ use Drupal\Component\Utility\NestedArray; use Drupal\block\BlockBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\menu_link\MenuTreeInterface; +use Drupal\Core\Menu\MenuLinkTreeInterface; +use Drupal\Core\Menu\MenuActiveTrailInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -27,13 +28,20 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface { /** - * The menu tree. + * The menu link tree service. * - * @var \Drupal\menu_link\MenuTreeInterface + * @var \Drupal\Core\Menu\MenuLinkTreeInterface */ protected $menuTree; /** + * The active menu trail service. + * + * @var \Drupal\Core\Menu\MenuActiveTrailInterface + */ + protected $menuActiveTrail; + + /** * Constructs a new SystemMenuBlock. * * @param array $configuration @@ -42,12 +50,15 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa * The plugin_id for the plugin instance. * @param array $plugin_definition * The plugin implementation definition. - * @param \Drupal\menu_link\MenuTreeInterface $menu_tree - * The menu tree. + * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree + * The menu tree service. + * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail + * The active menu trail service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuTreeInterface $menu_tree) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->menuTree = $menu_tree; + $this->menuActiveTrail = $menu_active_trail; } /** @@ -58,7 +69,8 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('menu_link.tree') + $container->get('menu.link_tree'), + $container->get('menu.active_trail') ); } @@ -66,23 +78,15 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function build() { - $menu = $this->getDerivativeId(); - return $this->menuTree->renderMenu($menu); - } - - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - // Modify the default max age for menu blocks: modifications made to menus, - // menu links and menu blocks will automatically invalidate corresponding - // cache tags, therefore allowing us to cache menu blocks forever. This is - // only not the case if there are user-specific or dynamic alterations (e.g. - // hook_node_access()), but in that: - // 1) it is possible to set a different max age for individual blocks, since - // this is just the default value. - // 2) modules can modify caching by implementing hook_block_view_alter() - return array('cache' => array('max_age' => \Drupal\Core\Cache\Cache::PERMANENT)); + $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); } /** @@ -91,9 +95,7 @@ public function defaultConfiguration() { public function getCacheKeys() { // Add a key for the active menu trail. $menu = $this->getDerivativeId(); - $active_trail = $this->menuTree->getActiveTrailIds($menu); - $active_trail_key = 'trail.' . implode('|', $active_trail); - return array_merge(parent::getCacheKeys(), array($active_trail_key)); + return array_merge(parent::getCacheKeys(), array($this->menuActiveTrail->getActiveTrailCacheKey($menu))); } /** @@ -108,13 +110,4 @@ public function getCacheTags() { return NestedArray::mergeDeep(parent::getCacheTags(), $tags); } - /** - * {@inheritdoc} - */ - 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'); - } - } diff --git a/core/modules/system/src/SystemManager.php b/core/modules/system/src/SystemManager.php index 696efc1..9027eb2 100644 --- a/core/modules/system/src/SystemManager.php +++ b/core/modules/system/src/SystemManager.php @@ -6,8 +6,11 @@ namespace Drupal\system; -use Drupal\Component\Utility\Unicode; +use Drupal\Core\Menu\MenuLinkTreeInterface; +use Drupal\Core\Menu\MenuLinkInterface; +use Drupal\Core\Menu\MenuActiveTrailInterface; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Menu\MenuTreeParameters; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -33,18 +36,25 @@ class SystemManager { protected $database; /** - * The menu link storage. + * The request stack. * - * @var \Drupal\menu_link\MenuLinkStorageInterface + * @var \Symfony\Component\HttpFoundation\RequestStack */ - protected $menuLinkStorage; + protected $requestStack; /** - * The request stack. + * The menu link tree manager. * - * @var \Symfony\Component\HttpFoundation\RequestStack + * @var \Drupal\Core\Menu\MenuLinkTreeInterface */ - protected $requestStack; + protected $menuTree; + + /** + * The active menu trail service. + * + * @var \Drupal\Core\Menu\MenuActiveTrailInterface + */ + protected $menuActiveTrail; /** * A static cache of menu items. @@ -79,12 +89,17 @@ 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) { + public function __construct(ModuleHandlerInterface $module_handler, Connection $database, EntityManagerInterface $entity_manager, RequestStack $request_stack, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) { $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; } /** @@ -171,11 +186,10 @@ public function getMaxSeverity(&$requirements) { * A render array suitable for drupal_render. */ public function getBlockContents() { - $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)) { + // We hard-code the menu name here since otherwise a link in the + // tools menu or elsewhere could give use a blank block. + $link = $this->menuActiveTrail->getActiveLink('admin'); + if ($link && $content = $this->getAdminBlock($link)) { $output = array( '#theme' => 'admin_block_content', '#content' => $content, @@ -192,48 +206,33 @@ public function getBlockContents() { /** * Provide a single block on the administration overview page. * - * @param \Drupal\menu_link\MenuLinkInterface|array $item + * @param \Drupal\Core\Menu\MenuLinkInterface $instance * The menu item to be displayed. * * @return array * An array of menu items, as expected by theme_admin_block_content(). */ - 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']]; - } - + public function getAdminBlock(MenuLinkInterface $instance) { $content = array(); - $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; - } + // Only find the children of this link. + $link_id = $instance->getPluginId(); + $parameters = new MenuTreeParameters(); + $parameters->setRoot($link_id)->excludeRoot()->topLevelOnly()->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(); } 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 b3378e2..310b356 100644 --- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php +++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php @@ -89,15 +89,10 @@ 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/item/$mlid_node_add/edit", $trail); + $this->assertBreadcrumb("admin/structure/menu/link/node.add_page/edit", $trail); $this->assertBreadcrumb('admin/structure/menu/manage/tools/add', $trail); // Verify Node administration breadcrumbs. @@ -171,7 +166,7 @@ function testBreadCrumbs() { // Alter node type menu settings. \Drupal::config("menu.entity.node.$type") ->set('available_menus', $menus) - ->set('parent', 'tools:0') + ->set('parent', 'tools:') ->save(); foreach ($menus as $menu) { @@ -180,13 +175,13 @@ function testBreadCrumbs() { $node2 = $this->drupalCreateNode(array( 'type' => $type, 'title' => $title, - 'menu' => entity_create('menu_link', array( - 'enabled' => 1, - 'link_title' => 'Parent ' . $title, + 'menu' => array( + 'hidden' => 0, + 'title' => 'Parent ' . $title, 'description' => '', 'menu_name' => $menu, - 'plid' => 0, - )), + 'parent' => '', + ), )); if ($menu == 'tools') { @@ -198,26 +193,26 @@ function testBreadCrumbs() { // link below it, and verify a full breadcrumb for the last child node. $menu = 'tools'; $edit = array( - 'link_title' => 'Root', - 'link_path' => 'node', + 'title[0][value]' => 'Root', + 'url' => 'node', ); $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => 'Root')); + $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => 'Root')); $link = reset($menu_links); $edit = array( - 'menu[parent]' => $link['menu_name'] . ':' . $link['mlid'], + 'menu[menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(), ); $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save and keep published')); $expected = array( - "node" => $link['link_title'], + "node" => $link->getTitle(), ); $trail = $home + $expected; $tree = $expected + array( - 'node/' . $parent->id() => $parent->menu['link_title'], + 'node/' . $parent->id() => $parent->menu['title'], ); $trail += array( - 'node/' . $parent->id() => $parent->menu['link_title'], + 'node/' . $parent->id() => $parent->menu['title'], ); // Add a taxonomy term/tag to last node, and add a link for that term to the @@ -247,32 +242,36 @@ function testBreadCrumbs() { } $parent_tid = $term->id(); } - $parent_mlid = 0; + $parent_mlid = ''; foreach ($tags as $name => $data) { $term = $data['term']; $edit = array( - 'link_title' => "$name link", - 'link_path' => "taxonomy/term/{$term->id()}", - 'parent' => "$menu:{$parent_mlid}", + 'title[0][value]' => "$name link", + 'url' => "taxonomy/term/{$term->id()}", + 'menu_parent' => "$menu:{$parent_mlid}", ); $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $edit['link_title'], 'link_path' => $edit['link_path'])); + $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())))); $tags[$name]['link'] = reset($menu_links); - $tags[$name]['link']['link_path'] = $edit['link_path']; - $parent_mlid = $tags[$name]['link']['mlid']; + $parent_mlid = $tags[$name]['link']->getPluginId(); } // 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['link_path'] => $link['link_title'], + $link_path => $link->getTitle(), ); - $this->assertBreadcrumb($link['link_path'], $trail, $term->getName(), $tree); + $this->assertBreadcrumb($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 @@ -281,14 +280,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['link_path']), + ':href' => url($link_path), )); - $this->assertTrue(count($elements) == 1, "Link to {$link['link_path']} appears only once."); + $this->assertTrue(count($elements) == 1, "Link to {$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['link_path'] => $term->getName(), + $link_path => $term->getName(), ); } @@ -298,7 +297,6 @@ function testBreadCrumbs() { user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array( 'access user profiles', )); - $this->drupalLogout(); // Verify breadcrumb on front page. $this->assertBreadcrumb('', array()); @@ -365,4 +363,5 @@ 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 d0db0de..716562a 100644 --- a/core/modules/system/src/Tests/Menu/LinksTest.php +++ b/core/modules/system/src/Tests/Menu/LinksTest.php @@ -12,6 +12,8 @@ /** * Tests for menu links. + * + * @todo: move this under menu_link_content module. */ class LinksTest extends WebTestBase { @@ -20,7 +22,14 @@ class LinksTest extends WebTestBase { * * @var array */ - public static $modules = array('router_test'); + public static $modules = array('router_test', 'menu_link_content'); + + /** + * The menu link plugin mananger + * + * @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager + */ + protected $menuLinkManager; public static function getInfo() { return array( @@ -36,6 +45,8 @@ public static function getInfo() { public function setUp() { parent::setUp(); + $this->menuLinkManager = $this->container->get('plugin.manager.menu.link'); + entity_create('menu', array( 'id' => 'menu_test', 'label' => 'Test menu', @@ -47,55 +58,60 @@ public function setUp() { * Create a simple hierarchy of links. */ function createLinkHierarchy($module = 'menu_test') { - // First remove all the menu links. - $menu_links = menu_link_load_multiple(); - menu_link_delete_multiple(array_keys($menu_links), TRUE, TRUE); + // First remove all the menu links in the menu. + $this->menuLinkManager->deleteLinksInMenu('menu_test'); // 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( - 'link_title' => 'Menu link test', - 'module' => $module, + 'title' => 'Menu link test', + 'provider' => $module, 'menu_name' => 'menu_test', + 'bundle' => 'menu_link_content' ); - $links['parent'] = $base_options + array( - 'link_path' => 'menu-test/parent', + $parent = $base_options + array( + 'route_name' => 'menu_test.hierarchy_parent', ); - $links['parent'] = entity_create('menu_link', $links['parent']); - $links['parent']->save(); + $link = entity_create('menu_link_content', $parent); + $link->save(); + $links['parent'] = $link->getPluginId(); - $links['child-1'] = $base_options + array( - 'link_path' => 'menu-test/parent/child-1', - 'plid' => $links['parent']['mlid'], + $child_1 = $base_options + array( + 'route_name' => 'menu_test.hierarchy_parent_child', + 'parent' => $links['parent'], ); - $links['child-1'] = entity_create('menu_link', $links['child-1']); - $links['child-1']->save(); + $link = entity_create('menu_link_content', $child_1); + $link->save(); + $links['child-1'] = $link->getPluginId(); - $links['child-1-1'] = $base_options + array( - 'link_path' => 'menu-test/parent/child-1/child-1-1', - 'plid' => $links['child-1']['mlid'], + $child_1_1 = $base_options + array( + 'route_name' => 'menu_test.hierarchy_parent_child2', + 'parent' => $links['child-1'], ); - $links['child-1-1'] = entity_create('menu_link', $links['child-1-1']); - $links['child-1-1']->save(); + $link = entity_create('menu_link_content', $child_1_1); + $link->save(); + $links['child-1-1'] = $link->getPluginId(); - $links['child-1-2'] = $base_options + array( - 'link_path' => 'menu-test/parent/child-1/child-1-2', - 'plid' => $links['child-1']['mlid'], + $child_1_2 = $base_options + array( + 'route_name' => 'menu_test.hierarchy_parent_child2', + 'parent' => $links['child-1'], ); - $links['child-1-2'] = entity_create('menu_link', $links['child-1-2']); - $links['child-1-2']->save(); + $link = entity_create('menu_link_content', $child_1_2); + $link->save(); + $links['child-1-2'] = $link->getPluginId(); - $links['child-2'] = $base_options + array( - 'link_path' => 'menu-test/parent/child-2', - 'plid' => $links['parent']['mlid'], + $child_2 = $base_options + array( + 'route_name' => 'menu_test.hierarchy_parent_child', + 'parent' => $links['parent'], ); - $links['child-2'] = entity_create('menu_link', $links['child-2']); - $links['child-2']->save(); + $link = entity_create('menu_link_content', $child_2); + $link->save(); + $links['child-2'] = $link->getPluginId(); return $links; } @@ -104,13 +120,12 @@ function createLinkHierarchy($module = 'menu_test') { * Assert that at set of links is properly parented. */ function assertMenuLinkParents($links, $expected_hierarchy) { - foreach ($expected_hierarchy as $child => $parent) { - $mlid = $links[$child]['mlid']; - $plid = $parent ? $links[$parent]['mlid'] : 0; + 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] : ''; - $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))); + $this->assertEqual($menu_link_plugin->getParent(), $expected_parent, format_string('Menu link %id has parent of %parent, expected %expected_parent.', array('%id' => $id, '%parent' => $menu_link_plugin->getParent(), '%expected_parent' => $expected_parent))); } } @@ -122,7 +137,7 @@ function testMenuLinkReparenting($module = 'menu_test') { $links = $this->createLinkHierarchy($module); $expected_hierarchy = array( - 'parent' => FALSE, + 'parent' => '', 'child-1' => 'parent', 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', @@ -133,11 +148,16 @@ 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); - $links['child-1']['plid'] = $links['child-2']['mlid']; - menu_link_save($links['child-1']); + /* @var \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin */ + $this->menuLinkManager->updateLink($links['child-1'], array('parent' => $links['child-2'])); + // Verify that the entity was updated too. + /* @var \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin */ + $menu_link_plugin = $this->menuLinkManager->createInstance($links['child-1']); + $entity = entity_load_by_uuid('menu_link_content', $menu_link_plugin->getDerivativeId()); + $this->assertEqual($entity->getParentId(), $links['child-2']); $expected_hierarchy = array( - 'parent' => FALSE, + 'parent' => '', 'child-1' => 'child-2', 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', @@ -146,29 +166,9 @@ 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. 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. + // have been reassigned to the parent. $links = $this->createLinkHierarchy($module); - // Don't do that at home. - entity_delete_multiple('menu_link', array($links['child-1']['mlid'])); + $this->menuLinkManager->deleteLink($links['child-1']); $expected_hierarchy = array( 'parent' => FALSE, @@ -178,119 +178,26 @@ function testMenuLinkReparenting($module = 'menu_test') { ); $this->assertMenuLinkParents($links, $expected_hierarchy); - // 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); + // @todo - figure out what makes sense to test in terms of automatic + // re-parenting. } /** - * Tests automatic reparenting. - * - * Runs tests on menu links defined by the menu_link.static service. + * Tests uninstalling a module providing default links. */ - 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 uninstall a module providing default links. - */ - public function testModuleUninstalledMenuLinks() { + public function XtestModuleUninstalledMenuLinks() { \Drupal::moduleHandler()->install(array('menu_test')); \Drupal::service('router.builder')->rebuild(); 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); + $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_test.menu_test'); $this->assertEqual(count($menu_links), 1); $menu_link = reset($menu_links); - $this->assertEqual($menu_link->machine_name, 'menu_test'); + $this->assertEqual($menu_link->getPluginId(), 'menu_test'); // Uninstall the module and ensure the menu link got removed. \Drupal::moduleHandler()->uninstall(array('menu_test')); - $result = $menu_link = \Drupal::entityQuery('menu_link')->condition('machine_name', 'menu_test')->execute(); - $menu_links = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($result); + menu_link_rebuild_defaults(); + $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_test.menu_test'); $this->assertEqual(count($menu_links), 0); } diff --git a/core/modules/system/src/Tests/Menu/MenuRouterTest.php b/core/modules/system/src/Tests/Menu/MenuRouterTest.php index 1b6bb2e..feb4e71 100644 --- a/core/modules/system/src/Tests/Menu/MenuRouterTest.php +++ b/core/modules/system/src/Tests/Menu/MenuRouterTest.php @@ -59,10 +59,6 @@ public function testMenuIntegration() { $this->doTestMenuOnRoute(); $this->doTestMenuName(); $this->doTestMenuLinkDefaultsAlter(); - $this->doTestMenuItemTitlesCases(); - $this->doTestMenuLinkMaintain(); - $this->doTestMenuLinkOptions(); - $this->doTestMenuItemHooks(); $this->doTestHookMenuIntegration(); $this->doTestExoticPath(); } @@ -115,68 +111,25 @@ protected function doTestDescriptionMenuItems() { } /** - * 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); - - $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test')); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('plugin.manager.menu.link'); + $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test'); $menu_link = reset($menu_links); - $this->assertEqual($menu_link->menu_name, 'original', 'Menu name is "original".'); + $this->assertEqual($menu_link->getMenuName(), 'original', 'Menu name is "original".'); // Change the menu_name parameter in menu_test.module, then force a menu // rebuild. menu_test_menu_name('changed'); - \Drupal::service('router.builder')->rebuild(); + $menu_link_manager->rebuild(); - $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test')); + $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test'); $menu_link = reset($menu_links); - $this->assertEqual($menu_link->menu_name, 'changed', 'Menu name was successfully changed after rebuild.'); + $this->assertEqual($menu_link->getMenuName(), 'changed', 'Menu name was successfully changed after rebuild.'); } /** @@ -185,9 +138,11 @@ protected function doTestMenuName() { 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. - $menu_links = entity_load_multiple_by_properties('menu_link', array('route_name' => 'menu_test.custom')); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('plugin.manager.menu.link'); + $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.custom'); $menu_link = reset($menu_links); - $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.'); + $this->assertEqual($menu_link->getPluginId(), '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_link_defaults_alter(). \Drupal::service('router.builder')->rebuild(); @@ -199,98 +154,17 @@ protected function doTestMenuLinkDefaultsAlter() { * Tests for menu hierarchy. */ protected function doTestMenuHierarchy() { - $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(); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('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.'); } /** diff --git a/core/modules/system/src/Tests/System/AdminTest.php b/core/modules/system/src/Tests/System/AdminTest.php index 839f3e0..0c842eb 100644 --- a/core/modules/system/src/Tests/System/AdminTest.php +++ b/core/modules/system/src/Tests/System/AdminTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\System; +use Drupal\Core\Menu\MenuTreeParameters; use Drupal\simpletest\WebTestBase; /** @@ -68,9 +69,10 @@ 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['title']); - $this->assertLinkByHref($item['link_path']); - $this->assertText($item['localized_options']['attributes']['title']); + $this->assertLink($item->getTitle()); + $this->assertLinkByHref($item->getUrlObject()->toString()); + // The description should appear below the link. + $this->assertText($item->getDescription()); } // For each administrative listing page on which the Locale module appears, @@ -125,26 +127,29 @@ function testAdminPages() { /** * Returns all top level menu links. * - * @return \Drupal\menu_link\MenuLinkInterface[] + * @return \Drupal\Core\Menu\MenuLinkInterface[] */ protected function getTopLevelMenuLinks() { - $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(); + /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */ + $menu_tree = $this->container->get('menu.link_tree'); + + // The system.admin link is normally the parent of all top-level admin links. + $parameters = new MenuTreeParameters(); + $parameters->setRoot('system.admin')->excludeRoot()->topLevelOnly()->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); - $menu_items = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($menu_link_ids); - foreach ($menu_items as &$menu_item) { - _menu_link_translate($menu_item); + // Transform the tree to a list of menu links. + $menu_links = array(); + foreach ($tree as $element) { + $menu_links[] = $element->link; } - return $menu_items; + + return $menu_links; } /** diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 9ba1a1c..4df299c 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -58,7 +58,7 @@ function template_preprocess_admin_block_content(&$variables) { $variables['attributes']['class'][] = 'compact'; } foreach ($variables['content'] as $key => $item) { - $variables['content'][$key]['link'] = l($item['title'], $item['link_path'], $item['localized_options']); + $variables['content'][$key]['link'] = \Drupal::linkGenerator()->generateFromUrl($item['title'], $item['url']); 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 461c431..ef02d89 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -397,13 +397,13 @@ function hook_page_build(&$page) { } /** - * Alter links for menus. + * Alter all the menu links discovered by the menu link plugin manager. * * @param array $links * The link definitions to be altered. * * @return array - * An array of default menu links. Each link has a key that is the machine + * An array of discovered menu links. Each link has a key that is the machine * name, which must be unique. By default, use the route name as the * machine name. In cases where multiple links use the same route name, such * as two links to the same page in different menus, or two links using the @@ -440,7 +440,7 @@ function hook_page_build(&$page) { * * @ingroup menu */ -function hook_menu_link_defaults_alter(&$links) { +function hook_menu_links_discovered_alter(&$links) { // Change the weight and title of the user.logout link. $links['user.logout']['weight'] = -10; $links['user.logout']['title'] = 'Logout'; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 3d67b65..b9cea5d 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -11,6 +11,7 @@ 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; @@ -1455,76 +1456,49 @@ function system_admin_compact_mode() { * An array of task links. */ function system_get_module_admin_tasks($module, array $info) { - $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; - } - } + $tree = &drupal_static(__FUNCTION__); + + /* @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu.link_tree'); + + 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); } $admin_tasks = array(); - $titles = array(); - foreach ($links as $item) { - if ($item['module'] != $module) { + foreach ($tree as $element) { + $link = $element->link; + if ($link->getProvider() != $module) { continue; } - $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; - } + $admin_tasks[] = array( + 'title' => $link->getTitle(), + 'description' => $link->getDescription(), + 'url' => $link->getUrlObject(), + ); } // Append link for permissions. if (\Drupal::moduleHandler()->implementsHook($module, 'permission')) { /** @var \Drupal\Core\Access\AccessManager $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())) { - $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, + /** @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( 'title' => t('Configure @module permissions', array('@module' => $info['name'])), - 'localized_options' => $options - )); - $admin_tasks["user.admin.people.permissions.$module"] = $menu_link; + 'description' => '', + 'url' => $url, + ); } } diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 35e17f7..c22d93b 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' - path: 'admin/config' + link_id: 'system.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' - path: 'admin/content' + link_id: 'system.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 2b7046c..9e09d19 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'] + arguments: ['@module_handler', '@database', '@entity.manager', '@request_stack', '@menu.link_tree', '@menu.active_trail'] 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 new file mode 100644 index 0000000..5560086 --- /dev/null +++ b/core/modules/system/templates/menu-tree.html.twig @@ -0,0 +1,40 @@ +{# +/** + * @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 }} + {%- else -%} + {{ heading.text }} + {%- endif -%} + {%- endif -%} + + {{ tree }} + +{%- endif %} 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 9f1d06d..b4f76a2 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -5,12 +5,10 @@ * Module that implements various hooks for menu tests. */ -use Drupal\menu_link\Entity\MenuLink; - /** - * Implements hook_menu_link_defaults_alter(). + * Implements hook_menu_links_discovered_alter(). */ -function menu_test_menu_link_defaults_alter(&$links) { +function menu_test_menu_links_discovered_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. @@ -97,29 +95,6 @@ function 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. * * @param bool $inherited @@ -167,44 +142,6 @@ function menu_test_menu_name($new_name = '') { } /** - * Implements hook_menu_link_insert(). - */ -function menu_test_menu_link_insert(MenuLink $item) { - menu_test_static_variable('insert'); -} - -/** - * Implements hook_menu_link_update(). - */ -function menu_test_menu_link_update(MenuLink $item) { - menu_test_static_variable('update'); -} - -/** - * Implements hook_menu_link_delete(). - */ -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. * * @param string $title diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.menu_links.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.menu_links.yml new file mode 100644 index 0000000..291fd70 --- /dev/null +++ b/core/modules/system/tests/modules/test_page_test/test_page_test.menu_links.yml @@ -0,0 +1,4 @@ +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 67eb561..4592769 100644 --- a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php +++ b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php @@ -131,30 +131,16 @@ function testModuleStatusChangeSubtreesHashCacheClear() { } /** - * Tests toolbar_menu_link_update() hook implementation. + * Tests toolbar cache tags implementation. */ function testMenuLinkUpdateSubtreesHashCacheClear() { - // 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); + // The ID of a (any) admin menu link. + $admin_menu_link_id = 'system.admin_config_development'; // Disable the link. $edit = array(); $edit['enabled'] = FALSE; - $this->drupalPostForm("admin/structure/menu/item/" . $link['mlid'] . "/edit", $edit, t('Save')); + $this->drupalPostForm("admin/structure/menu/link/" . $admin_menu_link_id . "/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 269f7ec..6967bf6 100644 --- a/core/modules/toolbar/toolbar.info.yml +++ b/core/modules/toolbar/toolbar.info.yml @@ -6,4 +6,3 @@ package: Core version: VERSION dependencies: - breakpoint - - menu_link diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index a8b4f08..2f34dfc 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; @@ -324,8 +324,6 @@ 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,33 +348,13 @@ 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; - $menu['toolbar_administration']['#attached']['js'][] = array( + $subtrees_attached['js'][] = array( 'type' => 'setting', 'data' => array('toolbar' => array( 'subtreesHash' => _toolbar_get_subtrees_hash($langcode), @@ -403,7 +381,19 @@ function toolbar_toolbar() { 'data-drupal-subtrees' => '', ), ), - 'tray' => $menu, + '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'), + ), + ), + ), '#weight' => -15, ); @@ -411,90 +401,99 @@ function toolbar_toolbar() { } /** - * Gets only the top level items below the 'admin' path. + * #pre_render callback to render the toolbar's administration tray. * - * @return - * An array containing a menu tree of top level items below the 'admin' path. + * @param array $element + * A renderable array. + * + * @return array + * The updated renderable array + * + * @see drupal_render() */ -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; +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()->topLevelOnly()->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; } /** - * Generates an array of links from a menu tree array. + * Menu link tree manipulator that adds toolbar-specific attributes. * - * Based on menu_navigation_links(). Adds path based IDs and icon placeholders - * to the links. + * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree + * The menu link tree to manipulate. * - * @return - * An array of links as defined above. + * @return \Drupal\Core\Menu\MenuLinkTreeElement[] + * The manipulated menu link tree. */ -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']); +function toolbar_menu_navigation_links(array $tree) { + foreach ($tree as $key => $element) { + if (!empty($element->subtree)) { + toolbar_menu_navigation_links($tree[$key]->subtree); } + // Make sure we have a path specific ID in place, so we can attach icons - // 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']), - ); + // 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(); + + $tree[$key]->options['attributes']['id'] = 'toolbar-link-' . $id; + $tree[$key]->options['attributes']['class'][] = 'toolbar-icon'; + $tree[$key]->options['attributes']['class'][] = 'toolbar-icon-' . strtolower(str_replace(' ', '-', $definition['title'])); + $tree[$key]->options['attributes']['title'] = String::checkPlain($link->getDescription()); } + return $tree; } /** * Returns the rendered subtree of each top-level toolbar link. */ function toolbar_get_rendered_subtrees() { + /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu.link_tree'); + $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(); - /** @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 = ''; - } - - $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['link_path']); - $subtrees[$id] = $subtree; + foreach ($tree as $element) { + /** @var \Drupal\Core\Menu\MenuLinkInterface $item */ + $link = $element->link; + if (!empty($element->subtree)) { + $subtree = $menu_tree->build($element->subtree); + $output = drupal_render($subtree); + } + else { + $output = ''; } + $id = str_replace(array('.', '<', '>'), array('-', '', '' ), $link->getUrlObject()->getRouteName()); + + $subtrees[$id] = $output; } return $subtrees; } @@ -521,7 +520,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,)); + \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, array('user' => array($uid), 'locale' => TRUE, 'menu' => 'admin')); } return $hash; } @@ -543,15 +542,6 @@ function toolbar_modules_uninstalled($modules) { /** * Implements hook_ENTITY_TYPE_update(). */ -function toolbar_menu_link_update(MenuLinkInterface $menu_link) { - if ($menu_link->menu_name === 'admin') { - _toolbar_clear_user_cache(); - } -} - -/** - * Implements hook_ENTITY_TYPE_update(). - */ function toolbar_user_update(UserInterface $user) { _toolbar_clear_user_cache($user->id()); } diff --git a/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php b/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php new file mode 100644 index 0000000..0eb46da --- /dev/null +++ b/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php @@ -0,0 +1,36 @@ +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 dd603d1..ed07028 100644 --- a/core/modules/user/src/Tests/UserAccountLinksTests.php +++ b/core/modules/user/src/Tests/UserAccountLinksTests.php @@ -7,6 +7,7 @@ namespace Drupal\user\Tests; +use Drupal\Core\Menu\MenuTreeParameters; use Drupal\simpletest\WebTestBase; /** @@ -49,14 +50,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' => 'links', + ':menu_class' => 'menu', ':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' => 'links', + ':menu_class' => 'menu', ':href' => 'user/logout', ':text' => 'Log out', )); @@ -67,13 +68,16 @@ function testSecondaryMenu() { $this->drupalGet(''); // For a logged-out user, expect no secondary links. - /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */ - $menu_tree = \Drupal::service('menu_link.tree'); - $tree = $menu_tree->buildTree('account'); + /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */ + $menu_tree = \Drupal::service('menu.link_tree'); + $tree = $menu_tree->load('account', new MenuTreeParameters()); + $manipulators = array( + array('callable' => 'menu.default_tree_manipulators:checkAccess'), + ); + $tree = $menu_tree->transform($tree, $manipulators); $this->assertEqual(count($tree), 1, 'The secondary links menu contains only one menu link.'); - $link = reset($tree); - $link = $link['link']; - $this->assertTrue((bool) $link->hidden, 'The menu link is hidden.'); + $element = reset($tree); + $this->assertTrue($element->link->isHidden(), 'The menu link is hidden.'); } /** @@ -86,7 +90,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' => 'links', + ':menu_class' => 'menu', ':href' => 'user', ':text' => 'My account', )); @@ -100,10 +104,7 @@ function testDisabledAccountLink() { $this->assertFieldChecked((string) $label[0], "The 'My account' link is enabled by default."); // Disable the 'My account' link. - $input = $this->xpath('//input[@id=:field_id]/@name', array(':field_id' => (string)$label[0])); - $edit = array( - (string) $input[0] => FALSE, - ); + $edit['links[menu_plugin_id:user.page][enabled]'] = FALSE; $this->drupalPostForm('admin/structure/menu/manage/account', $edit, t('Save')); // Get the homepage. @@ -111,7 +112,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' => 'links', + ':menu_class' => 'menu', ':href' => 'user', ':text' => 'My account', )); diff --git a/core/modules/user/user.menu_links.yml b/core/modules/user/user.menu_links.yml index bc421f0..3490a8d 100644 --- a/core/modules/user/user.menu_links.yml +++ b/core/modules/user/user.menu_links.yml @@ -3,6 +3,7 @@ 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 7856dfa..6995218 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -669,19 +669,6 @@ 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(). */ function user_menu_breadcrumb_alter(&$active_trail, $item) { @@ -693,16 +680,6 @@ 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; - } -} - -/** * Try to validate the user's login credentials locally. * * @param $name diff --git a/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php b/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php new file mode 100644 index 0000000..fa7aa02 --- /dev/null +++ b/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php @@ -0,0 +1,50 @@ +derivatives)) { + $this->getDerivativeDefinitions($base_plugin_definition); + } + if (isset($this->derivatives[$derivative_id])) { + return $this->derivatives[$derivative_id]; + } + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition) { + // @todo Decide what to do with all the crazy logic in views_menu_alter() in + // https://drupal.org/node/2107533. + $links = array(); + $views = Views::getApplicableViews('uses_hook_menu'); + foreach ($views as $data) { + /** @var \Drupal\views\ViewExecutable $view */ + list($view, $display_id) = $data; + $result = $view->executeHookMenuLinks($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 new file mode 100644 index 0000000..a6313b2 --- /dev/null +++ b/core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php @@ -0,0 +1,54 @@ + 'textfield', + '#title' => $this->t('Title'), + '#default_value' => $this->menuLink->getTitle(), + ); + + $form += parent::buildConfigurationForm($form, $form_state); + + if ($this->menuLink instanceof ViewsMenuLink) { + $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 !editlink.', array('!editlink' => \Drupal::l($label, 'views_ui.edit', array('view' => $id)))); + } + 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']; + + return $definition; + } + +} diff --git a/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php b/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php new file mode 100644 index 0000000..9464bd8 --- /dev/null +++ b/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php @@ -0,0 +1,168 @@ + 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 MenuLinkDefault. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\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 - can we get the translated value from the config without + // instantiating the view? + 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); + $display['display_options']['menu']['title'] = $new_definition_values['title']; + // @todo Note: This triggers a full rebuild of everything, even we just + // changed the title. + $view->storage->save(); + } + // Update the definition. + $this->pluginDefinition = $overrides + $this->pluginDefinition; + return $this->pluginDefinition; + } + + /** + * {@inheritdoc} + */ + public function getBaseId() { + $plugin_id = $this->getPluginId(); + if (strpos($plugin_id, 'views.') === 0) { + $plugin_id = 'views'; + } + return $plugin_id; + } + + /** + * {@inheritdoc} + */ + public function getDerivativeId() { + $plugin_id = $this->getPluginId(); + $derivative_id = NULL; + if (strpos($plugin_id, 'views.') === 0) { + list(, $derivative_id) = explode('views.', $plugin_id, 2); + } + return $derivative_id; + } +} diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index 288a84b..99882c6 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -2127,15 +2127,14 @@ public function renderMoreLink() { /** * Creates menu links, if this display provides some. * - * @param array $existing_links - * An array of already existing menu items provided by drupal. + * @internal param array $existing_links An array of already existing menu items provided by drupal.* An array of already existing menu items provided by drupal. * * @return array * The menu links registers for this display. * * @see hook_menu_link_defaults() */ - public function executeHookMenuLinkDefaults(array &$existing_links) { + public function executeHookMenuLinks() { 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 44127ec..c3a3b97 100644 --- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php @@ -278,8 +278,9 @@ public function alterRoutes(RouteCollection $collection) { /** * {@inheritdoc} + * @return array */ - public function executeHookMenuLinkDefaults(array &$existing_links) { + public function executeHookMenuLinks() { $links = array(); // Replace % with the link to our standard views argument loader @@ -299,7 +300,10 @@ public function executeHookMenuLinkDefaults(array &$existing_links) { $view_route_names = $this->state->get('views.view_route_names') ?: array(); $path = implode('/', $bits); - $menu_link_id = 'views.' . str_replace('/', '.', $path); + $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); if ($path) { $menu = $this->getOption('menu'); @@ -307,12 +311,11 @@ public function executeHookMenuLinkDefaults(array &$existing_links) { $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'), - 'machine_name' => $menu_link_id, + 'id' => $menu_link_id, ); $links[$menu_link_id]['title'] = $menu['title']; $links[$menu_link_id]['description'] = $menu['description']; @@ -323,6 +326,11 @@ public function executeHookMenuLinkDefaults(array &$existing_links) { // 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 a7d4418..d221297 100644 --- a/core/modules/views/src/Tests/Plugin/DisplayPageTest.php +++ b/core/modules/views/src/Tests/Plugin/DisplayPageTest.php @@ -7,6 +7,7 @@ namespace Drupal\views\Tests\Plugin; +use Drupal\Core\Session\AnonymousUserSession; use Drupal\views\Views; use Drupal\views\Tests\ViewUnitTestBase; use Symfony\Component\HttpFoundation\Request; @@ -31,7 +32,7 @@ class DisplayPageTest extends ViewUnitTestBase { * * @var array */ - public static $modules = array('system', 'user', 'menu_link', 'field', 'entity'); + public static $modules = array('system', 'user', 'field', 'entity'); /** * The router dumper to get all routes. @@ -56,13 +57,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 f294a89..9a93569 100644 --- a/core/modules/views/src/Tests/Wizard/MenuTest.php +++ b/core/modules/views/src/Tests/Wizard/MenuTest.php @@ -45,15 +45,10 @@ function testMenus() { $this->assertLinkByHref(url($view['page[path]'])); // Make sure the link is associated with the main menu. - $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]']))); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = $this->container->get('plugin.manager.menu.link'); + $link = $menu_link_manager->getDefinition('views_view:views.' . $view['id'] . '.page_1'); + $this->assertEqual($link['route_name'], 'view.' . $view['id'] . '.page_1', 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 ad8e801..7f01574 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -1513,13 +1513,12 @@ public function attachDisplays() { * * @param string $display_id * A display ID. - * @param array $links - * An array of default menu link items passed from + * @internal param array $links An array of default menu link items passed from* An array of default menu link items passed from * views_menu_link_defaults_alter(). * * @return array|bool */ - public function executeHookMenuLinkDefaults($display_id = NULL, &$links = array()) { + public function executeHookMenuLinks($display_id = NULL) { // 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)) { @@ -1528,7 +1527,7 @@ public function executeHookMenuLinkDefaults($display_id = NULL, &$links = array( // Execute the hook. if (isset($this->display_handler)) { - return $this->display_handler->executeHookMenuLinkDefaults($links); + return $this->display_handler->executeHookMenuLinks(); } } diff --git a/core/modules/views/views.menu_links.yml b/core/modules/views/views.menu_links.yml new file mode 100644 index 0000000..6188677 --- /dev/null +++ b/core/modules/views/views.menu_links.yml @@ -0,0 +1,4 @@ +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 886a599..21a767f 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -305,23 +305,6 @@ 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(). */ function views_page_alter(&$page) { diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml index 7029739..a1c07b2 100644 --- a/core/profiles/standard/standard.info.yml +++ b/core/profiles/standard/standard.info.yml @@ -14,6 +14,7 @@ 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 30656be..8df35e0 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -43,16 +43,11 @@ function standard_install() { ->fields(array('uid' => 1, 'rid' => 'administrator')) ->execute(); - // Create a Home link in the main menu. - $menu_link = entity_create('menu_link', array( - 'link_title' => t('Home'), - 'link_path' => '', - 'menu_name' => 'main', - )); - $menu_link->save(); - // Enable the Contact link in the footer menu. - menu_link_maintain('contact', 'enable', 'contact'); + /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ + $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); + $menu_link_manager->updateLink('contact.site_page', array('hidden' => 0)); + 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.menu_links.yml b/core/profiles/standard/standard.menu_links.yml new file mode 100644 index 0000000..2278b21 --- /dev/null +++ b/core/profiles/standard/standard.menu_links.yml @@ -0,0 +1,4 @@ +standard.front_page: + title: 'Home' + route_name: '' + menu_name: main diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme index 2503303..71d4a6d 100644 --- a/core/themes/bartik/bartik.theme +++ b/core/themes/bartik/bartik.theme @@ -55,18 +55,15 @@ function bartik_preprocess_page(&$variables) { // Store back the classes to the htmlpage object. $attributes['class'] = $classes; - // Pass the main menu and secondary menu to the template as render arrays. + // Set additional attributes on the primary and secondary navigation menus. if (!empty($variables['main_menu'])) { $variables['main_menu']['#attributes']['id'] = 'main-menu-links'; - $variables['main_menu']['#attributes']['class'] = array('links', 'clearfix'); + $variables['main_menu']['#attributes']['class'][] = 'links'; } if (!empty($variables['secondary_menu'])) { $variables['secondary_menu']['#attributes']['id'] = 'secondary-menu-links'; - $variables['secondary_menu']['#attributes']['class'] = array( - 'links', - 'inline', - 'clearfix', - ); + $variables['secondary_menu']['#attributes']['class'][] = 'links'; + $variables['secondary_menu']['#attributes']['class'][] = 'inline'; } // Set the options that apply to both page and maintenance page. @@ -138,10 +135,12 @@ function bartik_preprocess_block(&$variables) { } /** - * Implements THEME_menu_tree(). + * Implements hook_preprocess_HOOK() for menu-tree.html.twig. + * + * @see template_preprocess_menu_tree() */ -function bartik_menu_tree($variables) { - return ''; +function bartik_preprocess_menu_tree(&$variables) { + $variables['attributes']['class'][] = 'clearfix'; } /** diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index 5585065..81a531a 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -148,7 +148,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'] = url($item['link_path']); + $variables['content'][$key]['url'] = $item['url']->toString(); } } }