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 }}{{ heading.level }}>
+ {%- else -%}
+ {{ heading.text }}
+ {%- endif -%}
+ {%- endif -%}
+
+{%- 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();
}
}
}