diff --git a/core/core.services.yml b/core/core.services.yml
index 8e203ed..87947f2 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -59,6 +59,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:
@@ -236,6 +243,12 @@ services:
   plugin.manager.action:
     class: Drupal\Core\Action\ActionManager
     arguments: ['@container.namespaces', '@cache.discovery', '@language_manager', '@module_handler']
+  # @todo We seriously need to find a better name.
+  plugin.manager.menu.link_tree:
+    class: Drupal\Core\Menu\MenuLinkTree
+    arguments: ['@menu_link.tree_storage', '@menu_link.static.overrides', '@request_stack', '@router.route_provider', '@module_handler', '@cache.menu', '@language_manager', '@access_manager', '@current_user', '@entity.manager']
+  menu.link_tree:
+    alias: plugin.manager.menu.link_tree
   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']
@@ -247,6 +260,18 @@ services:
     arguments: ['@controller_resolver', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@request_stack']
   plugin.cache_clearer:
     class: Drupal\Core\Plugin\CachedDiscoveryClearer
+  paramconverter.menu_link:
+    class: Drupal\Core\ParamConverter\MenuLinkPluginConverter
+    tags:
+      - { name: paramconverter }
+    arguments: ['@plugin.manager.menu.link_tree']
+  menu_link.tree_storage:
+    class: Drupal\Core\Menu\MenuLinkTreeStorage
+    arguments: ['@database', '@url_generator']
+    public: false  # This service is private to plugin.manager.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 0fefe74..2f6ab00 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -123,125 +123,6 @@
  */
 
 /**
- * @defgroup menu_tree_parameters Menu tree parameters
- * @{
- * Parameters for a menu tree.
- */
-
-/**
- * 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
- *
- * @see menu_link_get_preferred()
- */
-const MENU_PREFERRED_LINK = '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91';
-
-/**
- * Localizes a menu link title using t() if possible.
- *
- * Translate the title and description to allow storage of English title
- * strings in the database, yet display of them in the language required
- * by the current user.
- *
- * @param $item
- *   A menu link entity.
- */
-function _menu_item_localize(&$item) {
-  // Allow default menu links to be translated.
-  $item['localized_options'] = $item['options'];
-  // All 'class' attributes are assumed to be an array during rendering, but
-  // links stored in the database may use an old string value.
-  // @todo In order to remove this code we need to implement a database update
-  //   including unserializing all existing link options and running this code
-  //   on them, as well as adding validation to menu_link_save().
-  if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
-    $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
-  }
-  // If the menu link is defined in code and not customized, we can use t().
-  if (!empty($item['machine_name']) && !$item['customized']) {
-    // @todo Figure out a proper way to support translations of menu links, see
-    //   https://drupal.org/node/2193777.
-    $item['title'] = t($item['link_title']);
-  }
-  else {
-    $item['title'] = $item['link_title'];
-  }
-}
-
-/**
- * Provides menu link unserializing, access control, and argument handling.
- *
- * @param array $item
- *   The passed in item has the following keys:
- *   - access: (optional) Becomes TRUE if the item is accessible, FALSE
- *     otherwise. If the key is not set, the access manager is used to
- *     determine the access.
- *   - options: (required) Is unserialized and copied to $item['localized_options'].
- *   - link_title: (required) The title of the menu link.
- *   - route_name: (required) The route name of the menu link.
- *   - route_parameters: (required) The unserialized route parameters of the menu link.
- *   The passed in item is changed by the following keys:
- *   - href: The actual path to the link. This path is generated from the
- *     link_path of the menu link entity.
- *   - title: The title of the link. This title is generated from the
- *     link_title of the menu link entity.
- */
-function _menu_link_translate(&$item) {
-  if (!is_array($item['options'])) {
-    $item['options'] = (array) unserialize($item['options']);
-  }
-  $item['localized_options'] = $item['options'];
-  $item['title'] = $item['link_title'];
-  if ($item['external'] || empty($item['route_name'])) {
-    $item['access'] = 1;
-    $item['href'] = $item['link_path'];
-    $item['route_parameters'] = array();
-    // Set to NULL so that drupal_pre_render_link() is certain to skip it.
-    $item['route_name'] = NULL;
-  }
-  else {
-    $item['href'] = NULL;
-    if (!is_array($item['route_parameters'])) {
-      $item['route_parameters'] = (array) unserialize($item['route_parameters']);
-    }
-    // menu_tree_check_access() may set this ahead of time for links to nodes.
-    if (!isset($item['access'])) {
-      $item['access'] = \Drupal::getContainer()->get('access_manager')->checkNamedRoute($item['route_name'], $item['route_parameters'], \Drupal::currentUser());
-    }
-    // For performance, don't localize a link the user can't access.
-    if ($item['access']) {
-      _menu_item_localize($item);
-    }
-  }
-
-  // Allow other customizations - e.g. adding a page-specific query string to the
-  // options array. For performance reasons we only invoke this hook if the link
-  // has the 'alter' flag set in the options array.
-  if (!empty($item['options']['alter'])) {
-    \Drupal::moduleHandler()->alter('translated_menu_link', $item, $map);
-  }
-}
-
-/**
  * Implements template_preprocess_HOOK() for theme_menu_tree().
  */
 function template_preprocess_menu_tree(&$variables) {
@@ -278,8 +159,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 '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
 }
 
@@ -436,15 +319,15 @@ function menu_navigation_links($menu_name, $level = 0) {
   }
 
   // Get the menu hierarchy for the current page.
-  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
-  $menu_tree = \Drupal::service('menu_link.tree');
+  /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $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 ($item['in_active_trail']) {
         // If the item is in the active trail, we continue in the subtree.
         $tree = empty($item['below']) ? array() : $item['below'];
         break;
@@ -455,25 +338,30 @@ function menu_navigation_links($menu_name, $level = 0) {
   // 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';
-      }
-      // Normally, l() compares the href of every link with the current path and
-      // sets the active class accordingly. But local tasks do not appear in
-      // menu trees, so if the current path is a local task, and this link is
-      // its tab root, then we have to set the class manually.
-      if ($item['link']['href'] != current_path()) {
-        $l['attributes']['class'][] = 'active';
-      }
-      // Keyed with the unique mlid to generate classes in links.html.twig.
-      $links['menu-' . $item['link']['mlid'] . $class] = $l;
+    /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+    $link = $item['link'];
+
+    if ($link->isHidden()) {
+      continue;
+    }
+
+    $class = '';
+    $url = $link->getUrlObject();
+    $l = $url->getOptions();
+    $l['title'] = $link->getTitle();
+    if ($url->isExternal()) {
+      $l['href'] = $url->getPath();
+    }
+    else {
+      $l['route_name'] = $url->getRouteName();
+      $l['route_parameters'] = $url->getRouteParameters();
+    }
+    if ($item['in_active_trail']) {
+      $class = ' active-trail';
+      $l['attributes']['class'][] = 'active-trail';
     }
+    // Keyed with the unique ID to generate classes in links.html.twig.
+    $links['menu-' . $link->getPluginId() . $class] = $l;
   }
   return $links;
 }
@@ -650,88 +538,6 @@ function menu_get_active_menu_names() {
 }
 
 /**
- * Looks up the preferred menu link for a given system path.
- *
- * @param $path
- *   The path; for example, 'node/5'. The function will find the corresponding
- *   menu link ('node/5' if it exists, or fallback to 'node/%').
- * @param $selected_menu
- *   The name of a menu used to restrict the search for a preferred menu link.
- *   If not specified, all the menus returned by menu_get_active_menu_names()
- *   will be used.
- *
- * @return
- *   A fully translated menu link, or FALSE if no matching menu link was
- *   found. The most specific menu link ('node/5' preferred over 'node/%') in
- *   the most preferred menu (as defined by menu_get_active_menu_names()) is
- *   returned.
- */
-function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
-  $preferred_links = &drupal_static(__FUNCTION__);
-
-  if (!isset($path)) {
-    $path = current_path();
-  }
-
-  if (empty($selected_menu)) {
-    // Use an illegal menu name as the key for the preferred menu link.
-    $selected_menu = MENU_PREFERRED_LINK;
-  }
-
-  if (!isset($preferred_links[$path])) {
-    // Look for the correct menu link by building a list of candidate paths,
-    // which are ordered by priority (translated hrefs are preferred over
-    // untranslated paths). Afterwards, the most relevant path is picked from
-    // the menus, ordered by menu preference.
-    $path_candidates = array();
-    // 1. The current item href.
-    // @todo simplify this code and convert to using route names.
-    // @see https://drupal.org/node/2154949
-    $path_candidates[$path] = $path;
-
-    // Retrieve a list of menu names, ordered by preference.
-    $menu_names = menu_get_active_menu_names();
-    // Put the selected menu at the front of the list.
-    array_unshift($menu_names, $selected_menu);
-
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $path_candidates));
-
-    // Sort candidates by link path and menu name.
-    $candidates = array();
-    foreach ($menu_links as $candidate) {
-      $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
-      // Add any menus not already in the menu name search list.
-      if (!in_array($candidate['menu_name'], $menu_names)) {
-        $menu_names[] = $candidate['menu_name'];
-      }
-    }
-
-    // Store the most specific link for each menu. Also save the most specific
-    // link of the most preferred menu in $preferred_link.
-    foreach ($path_candidates as $link_path) {
-      if (isset($candidates[$link_path])) {
-        foreach ($menu_names as $menu_name) {
-          if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
-            $candidate_item = $candidates[$link_path][$menu_name];
-            $candidate_item['access'] = \Drupal::service('access_manager')->checkNamedRoute($candidate_item['route_name'], $candidate_item['route_parameters'], \Drupal::currentUser());
-            if ($candidate_item['access']) {
-              _menu_item_localize($candidate_item);
-              $preferred_links[$path][$menu_name] = $candidate_item;
-              if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
-                // Store the most specific link.
-                $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
-  return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE;
-}
-
-/**
  * Clears all cached menu data.
  *
  * This should be called any time broad changes
@@ -739,197 +545,14 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
  */
 function menu_cache_clear_all() {
   \Drupal::cache('data')->deleteAll();
-  menu_reset_static_cache();
-}
-
-/**
- * Resets the menu system static cache.
- */
-function menu_reset_static_cache() {
-  \Drupal::entityManager()
-    ->getStorage('menu_link')->resetCache();
-  drupal_static_reset('menu_link_get_preferred');
-}
-
-/**
- * Saves menu links recursively for menu_links_rebuild_defaults().
- */
-function _menu_link_save_recursive($controller, $machine_name, &$children, &$links) {
-  $menu_link = $links[$machine_name];
-  if ($menu_link->isNew() || !$menu_link->customized) {
-    if (!isset($menu_link->plid) && !empty($menu_link->parent) && !empty($links[$menu_link->parent])) {
-      $parent = $links[$menu_link->parent];
-
-      if (empty($menu_link->menu_name) || $parent->menu_name == $menu_link->menu_name) {
-        $menu_link->plid = $parent->id();
-        $menu_link->menu_name = $parent->menu_name;
-      }
-    }
-    $controller->save($menu_link);
-  }
-  if (!empty($children[$machine_name])) {
-    foreach ($children[$machine_name] as $next_name) {
-      _menu_link_save_recursive($controller, $next_name, $children, $links);
-    }
-  }
-  // Remove processed link names so we can find stragglers.
-  unset($children[$machine_name]);
 }
 
 /**
  * Builds menu links for the items returned from the menu_link.static service.
  */
 function menu_link_rebuild_defaults() {
-  // Ensure that all configuration used to build the menu items are loaded
-  // without overrides.
-  $old_state = \Drupal::configFactory()->getOverrideState();
-  \Drupal::configFactory()->setOverrideState(FALSE);
-  $module_handler = \Drupal::moduleHandler();
-  if (!$module_handler->moduleExists('menu_link')) {
-    // The Menu link module may not be available during install, so rebuild
-    // when possible.
-    return;
-  }
-  /** @var \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage */
-  $menu_link_storage = \Drupal::entityManager()
-    ->getStorage('menu_link');
-  $links = array();
-  $children = array();
-  $top_links = array();
-  $all_links = \Drupal::service('menu_link.static')->getLinks();
-  if ($all_links) {
-    foreach ($all_links as $machine_name => $link) {
-      // For performance reasons, do a straight query now and convert to a menu
-      // link entity later.
-      // @todo revisit before release.
-      $existing_item = db_select('menu_links')
-        ->fields('menu_links')
-        ->condition('machine_name', $machine_name)
-        ->execute()->fetchObject();
-      if ($existing_item) {
-        $existing_item->options = unserialize($existing_item->options);
-        $existing_item->route_parameters = unserialize($existing_item->route_parameters);
-        $link['mlid'] = $existing_item->mlid;
-        $link['plid'] = $existing_item->plid;
-        $link['uuid'] = $existing_item->uuid;
-        $link['customized'] = $existing_item->customized;
-        $link['updated'] = $existing_item->updated;
-        $menu_link = $menu_link_storage->createFromDefaultLink($link);
-        // @todo Do not create a new entity in order to update it, see
-        //   https://drupal.org/node/2241865
-        $menu_link->setOriginalId($existing_item->mlid);
-
-        // Convert the existing item to a typed object.
-        /** @var \Drupal\menu_link\MenuLinkInterface $existing_item */
-        $existing_item = $menu_link_storage->create(get_object_vars($existing_item));
-
-        if (!$existing_item->customized) {
-          // A change in the default menu links may move the link to a
-          // different menu or parent.
-          if (!empty($link['menu_name']) && ($link['menu_name'] != $existing_item->menu_name)) {
-            $menu_link->plid = NULL;
-            $menu_link->menu_name = $link['menu_name'];
-          }
-          elseif (!empty($link['parent'])) {
-            $menu_link->plid = NULL;
-          }
-
-          $menu_link->original = $existing_item;
-        }
-      }
-      else {
-        if (empty($link['route_name']) && empty($link['link_path'])) {
-          watchdog('error', 'Menu_link %machine_name does neither provide a route_name nor a link_path, so it got skipped.', array('%machine_name' => $machine_name));
-          continue;
-        }
-        $menu_link = $menu_link_storage->createFromDefaultLink($link);
-      }
-      if (!empty($link['parent'])) {
-        $children[$link['parent']][$machine_name] = $machine_name;
-        $menu_link->parent = $link['parent'];
-        if (empty($link['menu_name'])) {
-          // Reset the default menu name so it is populated from the parent.
-          $menu_link->menu_name = NULL;
-        }
-      }
-      else {
-        // A top level link - we need them to root our tree.
-        $top_links[$machine_name] = $machine_name;
-        $menu_link->plid = 0;
-      }
-      $links[$machine_name] = $menu_link;
-    }
-  }
-  foreach ($top_links as $machine_name) {
-    _menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links);
-  }
-  // Handle any children we didn't find starting from top-level links.
-  foreach ($children as $orphan_links) {
-    foreach ($orphan_links as $machine_name) {
-      // Force it to the top level.
-      $links[$machine_name]->plid = 0;
-      _menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links);
-    }
-  }
-
-  // Find any item whose default menu link no longer exists.
-  if ($all_links) {
-    $query = \Drupal::entityQuery('menu_link')
-      ->condition('machine_name', array_keys($all_links), 'NOT IN')
-      ->exists('machine_name')
-      ->condition('external', 0)
-      ->condition('updated', 0)
-      ->condition('customized', 0)
-      ->sort('depth', 'DESC');
-    $result = $query->execute();
-  }
-  else {
-    $result = array();
-  }
-
-  // Remove all such items. Starting from those with the greatest depth will
-  // minimize the amount of re-parenting done by the menu link controller.
-  if ($result) {
-    menu_link_delete_multiple($result, TRUE);
-  }
-  \Drupal::configFactory()->setOverrideState($old_state);
-}
-
-/**
- * Returns an array containing all links for a menu.
- *
- * @param $menu_name
- *   The name of the menu whose links should be returned.
- *
- * @return
- *   An array of menu links.
- */
-function menu_load_links($menu_name) {
-  $links = array();
-
-  $query = \Drupal::entityQuery('menu_link')
-    ->condition('menu_name', $menu_name)
-    // Order by weight so as to be helpful for menus that are only one level
-    // deep.
-    ->sort('weight');
-  $result = $query->execute();
-
-  if (!empty($result)) {
-    $links = menu_link_load_multiple($result);
-  }
-
-  return $links;
-}
-
-/**
- * Deletes all links for a menu.
- *
- * @param $menu_name
- *   The name of the menu whose links will be deleted.
- */
-function menu_delete_links($menu_name) {
-  $links = menu_load_links($menu_name);
-  menu_link_delete_multiple(array_keys($links), FALSE, TRUE);
+  $menu_link_tree = \Drupal::service('plugin.manager.menu.link_tree');
+  $menu_link_tree->rebuild();
 }
 
 /**
@@ -943,37 +566,15 @@ function _menu_clear_page_cache() {
   if ($cache_cleared == 0) {
     Cache::invalidateTags(array('content' => TRUE));
     // Keep track of which menus have expanded items.
-    _menu_set_expanded_menus();
     $cache_cleared = 1;
   }
   elseif ($cache_cleared == 1) {
     drupal_register_shutdown_function('Drupal\Core\Cache\Cache::invalidateTags', array('content' => TRUE));
-    // Keep track of which menus have expanded items.
-    drupal_register_shutdown_function('_menu_set_expanded_menus');
     $cache_cleared = 2;
   }
 }
 
 /**
- * Updates a list of menus with expanded items.
- */
-function _menu_set_expanded_menus() {
-  $names = array();
-  $result = Drupal::entityQueryAggregate('menu_link')
-    ->condition('expanded', 0, '<>')
-    ->groupBy('menu_name')
-    ->execute();
-
-  // Flatten the resulting array.
-  foreach($result as $k => $v) {
-    $names[$k] = $v['menu_name'];
-  }
-
-  \Drupal::state()->set('menu_expanded', $names);
-}
-
-
-/**
  * Checks whether the site is in maintenance mode.
  *
  * This function will log the current user out and redirect to front page
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index a386e62..a5aafe9 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -413,12 +413,7 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
     }
 
     // Build array of fields to update or insert.
-    if (empty($info['serialize'])) {
-      $fields[$field] = $object->$field;
-    }
-    else {
-      $fields[$field] = serialize($object->$field);
-    }
+    $fields[$field] = $object->$field;
 
     // Type cast to proper datatype, except when the value is NULL and the
     // column allows this.
@@ -525,6 +520,9 @@ function drupal_schema_get_field_value(array $info, $value) {
     elseif (!is_array($value)) {
       $value = (string) $value;
     }
+    elseif (!empty($info['serialize'])) {
+      $value = serialize($value);
+    }
   }
   return $value;
 }
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 5c72435..2a5b828 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -622,4 +622,14 @@ public static function isConfigSyncing() {
     return static::$container->get('config.installer')->isSyncing();
   }
 
+  /**
+   * 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/Field/Plugin/Field/FieldType/MapItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/MapItem.php
index 26cdc41..2178761 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/MapItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/MapItem.php
@@ -97,4 +97,27 @@ public function __set($name, $value) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getValue($include_computed = FALSE) {
+    return isset($this->values['value']) ? $this->values['value'] : array();
+  }
+
+  /**
+   * Returns all values of a map
+   *
+   * @return array
+   */
+  public function getValues() {
+    return $this->values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return empty($this->values);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
new file mode 100644
index 0000000..cd66885
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\Form\MenuLinkDefaultForm.
+ */
+
+namespace Drupal\Core\Menu\Form;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuLinkTreeStorageInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class MenuLinkDefaultForm implements MenuLinkFormInterface, ContainerInjectionInterface {
+
+  /**
+   * @var \Drupal\Core\Menu\MenuLinkInterface
+   */
+  protected $menuLink;
+
+  /**
+   * The translation manager service.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translationManager;
+
+  /**
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuTree;
+
+  public function __construct(MenuLinkTreeInterface $menu_tree) {
+    $this->menuTree = $menu_tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('menu.link_tree')
+    );
+  }
+
+  /**
+   * Injects the menu link.
+   *
+   * @param MenuLinkInterface $menu_link
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link) {
+    $this->menuLink = $menu_link;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildEditForm(array &$form, array &$form_state) {
+    $form['#title'] = $this->t('Edit menu link %title', array('%title' => $this->menuLink->getTitle()));
+
+    $form['info'] = array(
+      '#type' => 'item',
+      '#title' => $this->t('This is a module-provided link. The label and path cannot be changed.'),
+    );
+    $form['path'] = array(
+      $this->menuLink->build(),
+      '#type' => 'item',
+      '#title' => $this->t('Link'),
+    );
+
+    $form['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enable menu link'),
+//      '#title_display' => 'invisible',
+      '#description' => $this->t('Menu links that are not enabled will not be listed in any menu.'),
+      '#default_value' => !$this->menuLink->isHidden(),
+    );
+     $form['expanded'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Show as expanded'),
+       '#description' => $this->t('If selected and this menu link has children, the menu will always appear expanded.'),
+      '#default_value' => $this->menuLink->isExpanded(),
+    );
+    // @TODO Should we expose expanded?
+    $delta = max(abs($this->menuLink->getWeight()), 50);
+    $form['weight'] = array(
+      '#type' => 'weight',
+      '#delta' => $delta,
+      '#default_value' => $this->menuLink->getWeight(),
+      '#title' => $this->t('Weight'),
+      '#description' => $this->t('Link weight among links in the same menu at the same depth.'),
+    );
+
+    $options = $this->menuTree->getParentSelectOptions($this->menuLink->getPluginId());
+    $menu_parent =  $this->menuLink->getMenuName() . ':' . $this->menuLink->getParent();
+
+    if (!isset($options[$menu_parent])) {
+      // Put it at the top level in the current menu.
+      $menu_parent = $this->menuLink->getMenuName() . ':';
+    }
+    $form['menu_parent'] = array(
+      '#type' => 'select',
+      '#title' => $this->t('Parent link'),
+      '#options' => $options,
+      '#default_value' => $menu_parent,
+      '#description' => $this->t('The maximum depth for a link and all its children is fixed at !maxdepth. Some menu links may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => $this->menuTree->maxDepth())),
+      '#attributes' => array('class' => array('menu-title-select')),
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFormValues(array &$form, array &$form_state) {
+    $new_definition = array();
+    $new_definition['hidden'] = $form_state['values']['enabled'] ? 0 : 1;
+    $new_definition['weight'] = (int) $form_state['values']['weight'];
+    $new_definition['expanded'] = $form_state['values']['expanded'] ? 1 : 0;
+    list($menu_name, $parent) = explode(':', $form_state['values']['menu_parent'], 2);
+    if (!empty($menu_name)) {
+      $new_definition['menu_name'] = $menu_name;
+    }
+    if (isset($parent)) {
+      $new_definition['parent'] = $parent;
+    }
+    return $new_definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateEditForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * Translates a string to the current language or to a given language.
+   *
+   * See the t() documentation for details.
+   */
+  protected function t($string, array $args = array(), array $options = array()) {
+    return $this->translationManager()->translate($string, $args, $options);
+  }
+
+  /**
+   * Gets the translation manager.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslationInterface
+   *   The translation manager.
+   */
+  protected function translationManager() {
+    if (!$this->translationManager) {
+      $this->translationManager = \Drupal::translation();
+    }
+    return $this->translationManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitEditForm(array &$form, array &$form_state) {
+    $new_definition = $this->extractFormValues($form, $form_state);
+
+    return $this->menuTree->updateLink($this->menuLink->getPluginId(), $new_definition);
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php b/core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php
new file mode 100644
index 0000000..7339fe3
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\Form\MenuLinkFormInterface.
+ */
+
+namespace Drupal\Core\Menu\Form;
+
+use Drupal\Core\Menu\MenuLinkInterface;
+
+interface MenuLinkFormInterface {
+
+  /**
+   * Injects the menu link.
+   *
+   * @param MenuLinkInterface $menu_link
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link);
+
+  /**
+   * Form constructor.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param array $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   The form structure.
+   */
+  public function buildEditForm(array &$form, array &$form_state);
+
+  /**
+   * Form validation handler.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param array $form_state
+   *   An associative array containing the current state of the form.
+   */
+  public function validateEditForm(array &$form, array &$form_state);
+
+  /**
+   * Form validation handler.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param array $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface
+   *   The updated instance.
+   */
+  public function submitEditForm(array &$form, array &$form_state);
+
+  public function extractFormValues(array &$form, array &$form_state);
+
+}
+
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkBase.php b/core/lib/Drupal/Core/Menu/MenuLinkBase.php
new file mode 100644
index 0000000..5d91e62
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuLinkBase.php
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\MenuLinkBase.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Url;
+use Drupal\Component\Plugin\Exception\PluginException;
+
+/**
+ * Base class used for MenuLink plugins.
+ */
+abstract class MenuLinkBase extends PluginBase implements MenuLinkInterface {
+
+  /**
+   * Defines all overrideable values.
+   *
+   * @var array
+   */
+  protected $overrideAllowed = array();
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build($title_attribute = TRUE) {
+    $options = $this->getOptions();
+    $description = $this->getDescription();
+    if ($title_attribute && $description) {
+      $options['attributes']['title'] = $description;
+    }
+    $build = array(
+      '#type' => 'link',
+      '#route_name' => $this->pluginDefinition['route_name'],
+      '#route_parameters' => $this->pluginDefinition['route_parameters'],
+      '#title' => $this->getTitle(),
+      '#options' => $options,
+    );
+    return $build;
+  }
+
+  /**
+   * Returns the weight of the menu link.
+   *
+   * @return int
+   *   The weight of the menu link, 0 by default.
+   */
+  public function getWeight() {
+    // By default the weight is 0.
+    if (!isset($this->pluginDefinition['weight'])) {
+      $this->pluginDefinition['weight'] = 0;
+    }
+    return $this->pluginDefinition['weight'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    // Subclasses may pull in the request or specific attributes as parameters.
+    $options = array();
+    if (!empty($this->pluginDefinition['title_context'])) {
+      $options['context'] = $this->pluginDefinition['title_context'];
+    }
+    $args = array();
+    if (isset($this->pluginDefinition['title_arguments']) && $title_arguments = $this->pluginDefinition['title_arguments']) {
+      $args = (array) $title_arguments;
+    }
+    return $this->t($this->pluginDefinition['title'], $args, $options);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMenuName() {
+    return $this->pluginDefinition['menu_name'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return $this->pluginDefinition['provider'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParent() {
+    return $this->pluginDefinition['parent'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isHidden() {
+    return (bool) $this->pluginDefinition['hidden'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExpanded() {
+    return (bool) $this->pluginDefinition['expanded'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isDiscovered() {
+    return (bool) $this->pluginDefinition['discovered'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isResetable() {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isDeletable() {
+    return (bool) $this->getDeleteRoute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    if ($this->pluginDefinition['description']) {
+      return $this->t($this->pluginDefinition['description']);
+    }
+    return '';
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptions() {
+    return $this->pluginDefinition['options'] ?: array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetaData() {
+    return $this->pluginDefinition['metadata'] ?: array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUrlObject($title_attribute = TRUE) {
+    $options = $this->getOptions();
+    $description = $this->getDescription();
+    if ($title_attribute && $description) {
+      $options['attributes']['title'] = $description;
+    }
+    if (empty($this->pluginDefinition['url'])) {
+      return new Url($this->pluginDefinition['route_name'], $this->pluginDefinition['route_parameters'], $options);
+    }
+    else {
+      $url = Url::createFromPath($this->pluginDefinition['url']);
+      $url->setOptions($options);
+      return $url;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormClass() {
+    return $this->pluginDefinition['form_class'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDeleteRoute() {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteLink() {
+    throw new PluginException(sprintf("Menu link plugin with ID %s does not support deletion", $this->getPluginId()));
+  }
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
new file mode 100644
index 0000000..ece7ab0
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\MenuLinkDefault.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Default object used for MenuLink plugins.
+ */
+class MenuLinkDefault extends MenuLinkBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var array
+   */
+  protected $overrideAllowed = array(
+    'menu_name' => 1,
+    'parent' => 1,
+    'weight' => 1,
+    'expanded' => 1,
+    'hidden' => 1,
+  );
+
+  /**
+   * @var \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
+   */
+  protected $staticOverride;
+
+  /**
+   * Constructs a new MenuLinkDefault.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
+   *   The static override storage.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->staticOverride = $static_override;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('menu_link.static.overrides')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isResetable() {
+    // The link can be reset if it was discovered and has an override.
+    return $this->pluginDefinition['discovered'] && $this->staticOverride->loadOverride($this->getPluginId());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateLink(array $new_definition_values, $persist) {
+    $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
+    if ($persist) {
+      $this->staticOverride->saveOverride($this->getPluginId(), $overrides);
+    }
+    // Update the definition.
+    $this->pluginDefinition = $overrides + $this->getPluginDefinition();
+    return $this->pluginDefinition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function persistLinkDeletion() {
+    // @todo - what should this do by default?
+  }
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkInterface.php b/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
new file mode 100644
index 0000000..b6db560
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\MenuLinkInterface.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Component\Plugin\DerivativeInspectionInterface;
+
+/**
+ * Default object used for LocalTaskPlugins.
+ */
+interface MenuLinkInterface extends PluginInspectionInterface, DerivativeInspectionInterface {
+
+  /**
+   * Return a localized link render array
+   *
+   * @param bool $title_attribute
+   *   If TRUE, add the link description (if present) as the title attribute.
+   *
+   * @return array
+   */
+  public function build($title_attribute = TRUE);
+
+  /**
+   * Returns the weight of the menu link.
+   *
+   * @return int
+   *   The weight of the menu link, 0 by default.
+   */
+  public function getWeight();
+
+  /**
+   * Returns the localized title to be shown for this link.
+   *
+   * @return string
+   *   The title of the menu link.
+   */
+  public function getTitle();
+
+  /**
+   * Returns the description of the menu link.
+   */
+  public function getDescription();
+
+  /**
+   * Returns the menu name of the menu link.
+   */
+  public function getMenuName();
+
+  /**
+   * Returns the provider (module name) of the menu link.
+   */
+  public function getProvider();
+
+  /**
+   * Returns the plugin ID of the menu link's parent, or an empty string.
+   */
+  public function getParent();
+
+  /**
+   * @return bool
+   */
+  public function isHidden();
+
+  /**
+   * Returns whether the child menu links should always been shown.
+   *
+   * @return bool
+   */
+  public function isExpanded();
+
+  /**
+   * Returns whether this link was discovered.
+   *
+   * @return bool
+   */
+  public function isDiscovered();
+
+  /**
+   * Returns whether this link can be reset.
+   *
+   * In general, only links that store overrides using the
+   * menu_link.static.overrides service should return TRUE for this method.
+   *
+   * @return bool
+   */
+  public function isResetable();
+
+  /**
+   * Returns whether this link can be deleted.
+   *
+   * @return bool
+   */
+  public function isDeletable();
+
+  /**
+   * Returns the URL object containing either the external path or route.
+   *
+   * @param bool $title_attribute
+   *   If TRUE, add the link description (if present) as the title attribute.
+   *
+   * @return \Drupal\Core\Url
+   */
+  public function getUrlObject($title_attribute = TRUE);
+
+  /**
+   * Returns the options for this link.
+   *
+   * @return array
+   *   The options for the menu link.
+   */
+  public function getOptions();
+
+  /**
+   * Returns any metadata for this link.
+   *
+   * @return array
+   *   The metadata for the menu link.
+   */
+  public function getMetaData();
+
+  /**
+   * Update and save values for a menu link.
+   *
+   * The override is written depending on the implementation.
+   * Static links, for example, have a dedicated override storage service.
+   *
+   * Depending on the implementation details of the class, not all definition
+   * values may be changed. For example, changes to the title of a static
+   * link will be discarded.
+   *
+   * In general, this method should not be called directly, but will be called
+   * automatically from MenuLinkTreeInterface::updateLink()
+   *
+   * @param array $new_definition_values
+   *   The new values for the link definition. This will usually be just a
+   *   subset of the plugin definition.
+   * @param bool $persist
+   *   TRUE to have the link persist the changed values to any additional
+   *   storage.
+   *
+   * @return array
+   *   The plugin definition incorporating any allowed changes.
+   */
+  public function updateLink(array $new_definition_values, $persist);
+
+  /**
+   * Delete a menu link.
+   *
+   * In general, this method should not be called directly, but will be called
+   * automatically from MenuLinkTreeInterface::deleteLink()
+   *
+   * This method will only delete the link from any additional storage, but
+   * not from the menu.link_tree service.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   If the link is not deletable.
+   */
+  public function deleteLink();
+
+  /**
+   * Returns the name of a class that can build an editing form for this link.
+   */
+  public function getFormClass();
+
+  /**
+   * Returns parameters for a delete link, or an empty value.
+   *
+   * @return array
+   *   route_name and route_parameters
+   */
+  public function getDeleteRoute();
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTree.php b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
new file mode 100644
index 0000000..0ea153d
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
@@ -0,0 +1,982 @@
+<?php
+
+/**
+* @file
+* Contains \Drupal\Core\Menu\MenuLinkTree.
+*/
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Cache\Cache;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Access\AccessManager;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\YamlDiscovery;
+use Drupal\Core\Plugin\Factory\ContainerFactory;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+* Manages discovery, instantiation, and tree building of menu link plugins.
+*
+* This manager finds plugins that are rendered as menu links.
+*/
+class MenuLinkTree implements MenuLinkTreeInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaults = array(
+    // (required) The name of the menu for this link.
+    'menu_name' => 'tools',
+    // (required) The name of the route this links to, unless it's external.
+    'route_name' => '',
+    // Parameters for route variables when generating a link.
+    'route_parameters' => array(),
+    // The external URL if this link references one (required if route_name is empty).
+    'url' => '',
+    // The static title for the menu link.
+    'title' => '',
+    'title_arguments' => array(),
+    'title_context' => '',
+    // The description.
+    'description' => '',
+    // The plugin ID of the parent link (or NULL for a top-level link).
+    'parent' => '',
+    // The weight of the link.
+    'weight' => 0,
+    // The default link options.
+    'options' => array(),
+    'expanded' => 0,
+    'hidden' => 0,
+    // Flag for whether this plugin was discovered. Should be set to 0 or NULL
+    // for definitions that are added via a direct save.
+    'discovered' => 0,
+    'provider' => '',
+    'metadata' => array(),
+    // Default class for local task implementations.
+    'class' => 'Drupal\Core\Menu\MenuLinkDefault',
+    'form_class' => 'Drupal\Core\Menu\Form\MenuLinkDefaultForm',
+    // The plugin id. Set by the plugin system based on the top-level YAML key.
+    'id' => '',
+  );
+
+  /**
+   * The object that discovers plugins managed by this manager.
+   *
+   * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
+   */
+  protected $discovery;
+
+  /**
+   * The object that instantiates plugins managed by this manager.
+   *
+   * @var \Drupal\Component\Plugin\Factory\FactoryInterface
+   */
+  protected $factory;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * Cache backend instance for the extracted tree data.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $treeCacheBackend;
+
+  /**
+   * The menu link tree storage.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeStorageInterface
+   */
+  protected $treeStorage;
+
+  /**
+   * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
+   *   Service providing overrides for static links
+   */
+  protected $overrides;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The plugin instances.
+   *
+   * @var array
+   */
+  protected $instances = array();
+
+  /**
+   * The statically cached definitions.
+   *
+   * @var array
+   */
+  protected $definitions = array();
+
+  /**
+   * The route provider to load routes by name.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * The access manager.
+   *
+   * @var \Drupal\Core\Access\AccessManager
+   */
+  protected $accessManager;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Stores the menu tree used by the doBuildTree method, keyed by a cache ID.
+   *
+   * This cache ID is built using the $menu_name, the current language and
+   * some parameters passed into an entity query.
+   */
+  protected $menuTree;
+
+  /**
+   * Stores the menu tree data on the current page keyed by a cache ID.
+   *
+   * This contains less information than a tree built with buildAllData.
+   *
+   * @var array
+   */
+  protected $menuPageTrees;
+
+  protected $preferredLinks = array();
+  protected $buildAllDataParameters = array();
+
+  /**
+   * Constructs a \Drupal\Core\Menu\MenuLinkTree object.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkTreeStorageInterface $tree_storage
+   *   The menu link tree storage.
+   * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $overrides
+   *   Service providing overrides for static links
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request object to use for building titles and paths for plugin instances.
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider to load routes by name.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $tree_cache_backend
+   *   Cache backend instance for the extracted tree data.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Access\AccessManager $access_manager
+   *   The access manager.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(MenuLinkTreeStorageInterface $tree_storage, StaticMenuLinkOverridesInterface $overrides, RequestStack $request_stack, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $tree_cache_backend, LanguageManagerInterface $language_manager, AccessManager $access_manager, AccountInterface $account, EntityManagerInterface $entity_manager) {
+    $this->treeStorage = $tree_storage;
+    $this->overrides = $overrides;
+    $this->factory = new ContainerFactory($this);
+    $this->requestStack = $request_stack;
+    $this->routeProvider = $route_provider;
+    $this->accessManager = $access_manager;
+    $this->account = $account;
+    $this->moduleHandler = $module_handler;
+    $this->treeCacheBackend = $tree_cache_backend;
+    $this->languageManager = $language_manager;
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    $definition = NestedArray::mergeDeep($this->defaults, $definition);
+    $definition['parent'] = (string) $definition['parent'];
+    $definition['id'] = $plugin_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinitions() {
+    // Since this function is called rarely, instantiate the discovery here.
+    if (empty($this->discovery)) {
+      $yaml = new YamlDiscovery('menu_links', $this->moduleHandler->getModuleDirectories());
+      $this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml);
+    }
+    $definitions = $this->discovery->getDefinitions();
+
+    $this->moduleHandler->alter('menu_links', $definitions);
+
+    foreach ($definitions as $plugin_id => &$definition) {
+      $definition['id'] = $plugin_id;
+      $this->processDefinition($definition, $plugin_id);
+    }
+
+    // If this plugin was provided by a module that does not exist, remove the
+    // plugin definition.
+    foreach ($definitions as $plugin_id => $plugin_definition) {
+      if (!empty($plugin_definition['provider']) && !$this->moduleHandler->moduleExists($plugin_definition['provider'])) {
+        unset($definitions[$plugin_id]);
+      }
+      else {
+        // Any link found here is flagged as discovered, so it can be purged
+        // if it does exit in the future.
+        $definitions[$plugin_id]['discovered'] = 1;
+      }
+    }
+    return $definitions;
+  }
+
+  public function rebuild() {
+    // Fetch the list of existing menus, in case some are not longer populated
+    // after the rebuild.
+    $before_menus = $this->treeStorage->getMenuNames();
+    $definitions = $this->getDefinitions();
+    // Apply overrides from config.
+    $overrides = $this->overrides->loadMultipleOverrides(array_keys($definitions));
+    foreach ($overrides as $id => $changes) {
+      if (!empty($definitions[$id])) {
+        $definitions[$id] = $changes + $definitions[$id];
+      }
+    }
+    $this->treeStorage->rebuild($definitions);
+    $this->treeCacheBackend->deleteAll();
+    $affected_menus = $this->treeStorage->getMenuNames() + $before_menus;
+    Cache::invalidateTags(array('menu' => $affected_menus));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinition($plugin_id) {
+    // When building tress, we will usually have the definitions already loaded.
+    // This makes the call to $this->factory->createInstance() faster.
+    if (!isset($this->definitions[$plugin_id])) {
+      $this->definitions[$plugin_id] = $this->treeStorage->load($plugin_id);
+    }
+    return $this->definitions[$plugin_id];
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface
+   */
+  public function createInstance($plugin_id, array $configuration = array()) {
+    return $this->factory->createInstance($plugin_id, $configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInstance(array $options) {
+    if (isset($options['id'])) {
+      return $this->createInstance($options['id']);
+    }
+  }
+
+  /**
+   * Returns an array containing all links for a menu.
+   *
+   * @param string $menu_name
+   *   The name of the menu whose links should be returned.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface[]
+   *   An array of menu link plugin instances keyed by ID.
+   */
+  public function loadLinks($menu_name) {
+    $instances = array();
+    $loaded = $this->treeStorage->loadByProperties(array('menu_name' => $menu_name));
+    foreach ($loaded as $plugin_id => $definition) {
+      // Setting the definition here means it will be used by getDefinition()
+      // which is called by createInstance() from the factory.
+      $this->definitions[$plugin_id] = $definition;
+      $instances[$plugin_id] = $this->createInstance($plugin_id);
+    }
+    return $instances;
+  }
+
+  /**
+   * Deletes all links for a menu.
+   *
+   * @todo - this should really only be called as part of the flow of
+   * deleting a menu entity, so maybe we should load it and make sure it's
+   * not locked?
+   *
+   * @param $menu_name
+   *   The name of the menu whose links will be deleted.
+   */
+  public function deleteLinksInMenu($menu_name) {
+    $affected_menus = array($menu_name => $menu_name);
+    foreach ($this->treeStorage->loadByProperties(array('menu_tree')) as $plugin_id => $definition) {
+      // Setting the definition here means it will be used by getDefinition()
+      // which is called by createInstance() from the factory.
+      $this->definitions[$plugin_id] = $definition;
+      $instance = $this->createInstance($plugin_id);
+      if ($instance->isResetable()) {
+        $new_instance = $this->resetInstance($instance);
+        $affected_menus[$new_instance->getMenuName()] = $new_instance->getMenuName();
+      }
+      elseif ($instance->isDeletable()) {
+        $this->deleteInstance($instance, TRUE);
+      }
+    }
+    Cache::invalidateTags(array('menu' => $affected_menus));
+  }
+
+  protected function deleteInstance(MenuLinkInterface $instance, $persist) {
+    $id = $instance->getPluginId();
+    if ($instance->isDeletable()) {
+      if ($persist) {
+        $instance->deleteLink();
+      }
+    }
+    else {
+      throw new PluginException(sprintf("Menu link plugin with ID %s does not support deletion", $id));
+    }
+    $this->treeStorage->delete($id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteLink($id, $persist = TRUE) {
+    $definition = $this->treeStorage->load($id);
+    // It's possible the definition has already been deleted, or doesn't exist.
+    if ($definition) {
+      // Setting the definition here means it will be used by getDefinition()
+      // which is called by createInstance() from the factory.
+      $this->definitions[$id] = $definition;
+      $instance = $this->createInstance($id);
+      $this->deleteInstance($instance, $persist);
+      // Many children may have moved.
+      $this->resetDefinitions();
+      Cache::invalidateTags(array('menu' => array($definition['menu_name'])));
+    }
+    $this->resetDefinition($id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function countMenuLinks($menu_name = NULL) {
+    return $this->treeStorage->countMenuLinks($menu_name);
+  }
+
+  public function loadLinksByRoute($route_name, array $route_parameters = array(), $include_hidden = FALSE) {
+    $instances = array();
+    $loaded = $this->treeStorage->loadByRoute($route_name, $route_parameters, $include_hidden);
+    foreach ($loaded as $plugin_id => $definition) {
+      // Setting the definition here means it will be used by getDefinition()
+      // which is called by createInstance() from the factory.
+      $this->definitions[$plugin_id] = $definition;
+      $instances[$plugin_id] = $this->createInstance($plugin_id);
+    }
+    return $instances;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function maxDepth() {
+    return $this->treeStorage->maxDepth();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRenderTree($tree) {
+    $build = array();
+
+    foreach ($tree as $data) {
+      $class = array();
+      /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+      $link = $data['link'];
+      // Generally we only deal with visible links, but just in case.
+      if ($link->isHidden()) {
+        continue;
+      }
+      // Set a class for the <li>-tag. Only set 'expanded' class if the link
+      // also has visible children within the current tree.
+      if ($data['has_children'] && $data['below']) {
+        $class[] = 'expanded';
+      }
+      elseif ($data['has_children']) {
+        $class[] = 'collapsed';
+      }
+      else {
+        $class[] = 'leaf';
+      }
+      // Set a class if the link is in the active trail.
+      if ($data['in_active_trail']) {
+        $class[] = 'active-trail';
+        //$data['link']['localized_options']['attributes']['class'][] = 'active-trail';
+      }
+
+      // Allow menu-specific theme overrides.
+      $element['#theme'] = 'menu_link__' . strtr($link->getMenuName(), '-', '_');
+      $element['#attributes']['class'] = $class;
+      $element['#title'] = $link->getTitle();
+      // @todo Use route name and parameters to generate the link path, unless
+      //    it is external.
+      $element['#url'] = $link->getUrlObject();
+      $element['#below'] = $data['below'] ? $this->buildRenderTree($data['below']) : array();
+      $element['#original_link'] = $link;
+      // Index using the link's unique ID.
+      $build[$link->getPluginId()] = $element;
+    }
+    if ($build) {
+      // Make sure drupal_render() does not re-order the links.
+      $build['#sorted'] = TRUE;
+      // Get the menu name from the last link.
+      $menu_name = $link->getMenuName();
+      // Add the theme wrapper for outer markup.
+      // Allow menu-specific theme overrides.
+      $build['#theme_wrappers'][] = 'menu_tree__' . strtr($menu_name, '-', '_');
+      // Set cache tag.
+      $build['#cache']['tags']['menu'][$menu_name] = $menu_name;
+    }
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getActiveTrailIds($menu_name) {
+    // Parent ids; used both as key and value to ensure uniqueness.
+    // We always want all the top-level links with parent == ''.
+    $active_trail = array('' => '');
+
+    $request = $this->requestStack->getCurrentRequest();
+
+    if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
+      $route_parameters = $request->attributes->get('_raw_variables')->all();
+      // @todo https://drupal.org/node/2068471 is adding support so we can tell
+      // if this is called on a 404/403 page.
+      // Find a menu link corresponding to the current path. If
+      // $active_path is NULL, let $this->menuLinkGetPreferred() determine the
+      // path.
+      if ($active_link = $this->menuLinkGetPreferred($route_name, $route_parameters, $menu_name)) {
+        if ($active_link->getMenuName() == $menu_name) {
+          $active_trail += $this->treeStorage->getMaterializedPathIds($active_link->getPluginId());
+        }
+      }
+    }
+    return $active_trail;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function menuLinkGetPreferred($route_name = NULL, array $route_parameters = array(), $selected_menu = NULL) {
+    if (!isset($route_name)) {
+      $request = $this->requestStack->getCurrentRequest();
+
+      $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
+      $route_parameters = $request->attributes->get('_raw_variables')->all();
+    }
+
+    $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account);
+    if (!$access) {
+      return NULL;
+    }
+    asort($route_parameters);
+    $route_key = hash('sha256', $route_name . serialize($route_parameters));
+
+    if (empty($selected_menu)) {
+      // Use an illegal menu name as the key for the preferred menu link.
+      $selected_menu = '%';
+    }
+
+    if (!isset($this->preferredLinks[$route_key])) {
+      // Retrieve a list of menu names, ordered by preference.
+      $menu_names = $this->menuGetActiveMenuNames();
+      // Put the selected menu at the front of the list.
+      array_unshift($menu_names, $selected_menu);
+      // If this menu name is not fond later, we want to just get NULL.
+      $this->preferredLinks[$route_key][$selected_menu] = NULL;
+
+      // Only load non-hidden links.
+      $definitions = $this->treeStorage->loadByRoute($route_name, $route_parameters);
+      // Sort candidates by menu name.
+      $candidates = array();
+      foreach ($definitions as $candidate) {
+        $candidates[$candidate['menu_name']] = $candidate;
+        $menu_names[] = $candidate['menu_name'];
+      }
+      foreach ($menu_names as $menu_name) {
+        if (isset($candidates[$menu_name]) && !isset($this->preferredLinks[$route_key][$menu_name])) {
+          $candidate = $candidates[$menu_name];
+          $this->definitions[$candidate['id']] = $candidate;
+          $instance = $this->createInstance($candidate['id']);
+          $this->preferredLinks[$route_key][$menu_name] = $instance;
+          if (!isset($this->preferredLinks[$route_key]['%'])) {
+            $this->preferredLinks[$route_key]['%'] = $instance;
+          }
+        }
+      }
+
+    }
+    return isset($this->preferredLinks[$route_key][$selected_menu]) ? $this->preferredLinks[$route_key][$selected_menu] : NULL;
+  }
+
+  protected function menuGetActiveMenuNames() {
+    return menu_get_active_menu_names();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
+    $language_interface = $this->languageManager->getCurrentLanguage();
+
+    // Load the request corresponding to the current page.
+    $request = $this->requestStack->getCurrentRequest();
+    $page_is_403 = FALSE;
+    $system_path = NULL;
+    if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
+      $system_path = $request->attributes->get('_system_path');
+      $page_is_403 = $request->attributes->get('_exception_statuscode') == 403;
+    }
+
+    if (isset($max_depth)) {
+      $max_depth = min($max_depth, $this->treeStorage->maxDepth());
+    }
+    // Generate a cache ID (cid) specific for this page.
+    $cid = 'links:' . $menu_name . ':page:' . $system_path . ':' . $language_interface->id . ':' . $page_is_403 . ':' . (int) $max_depth;
+    // If we are asked for the active trail only, and $menu_name has not been
+    // built and cached for this page yet, then this likely means that it
+    // won't be built anymore, as this function is invoked from
+    // template_preprocess_page(). So in order to not build a giant menu tree
+    // that needs to be checked for access on all levels, we simply check
+    // whether we have the menu already in cache, or otherwise, build a
+    // minimum tree containing the active trail only.
+    // @see menu_set_active_trail()
+    if (!isset($this->menuPageTrees[$cid]) && $only_active_trail) {
+      $cid .= ':trail';
+    }
+
+    // @TODO Decide whether it makes sense to static cache page menu trees.
+    if (!isset($this->menuPageTrees[$cid])) {
+      // If the static variable doesn't have the data, check {cache_menu}.
+      $cache = $this->treeCacheBackend->get($cid);
+      if ($cache && $cache->data) {
+        // If the cache entry exists, it contains the parameters for
+        // menu_build_tree().
+        $tree_parameters = $cache->data;
+      }
+      else {
+        $tree_parameters = $this->doBuildPageDataTreeParameters($menu_name, $max_depth, $only_active_trail, $page_is_403);
+
+        // Cache the tree building parameters using the page-specific cid.
+        $this->treeCacheBackend->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
+      }
+
+      // Build the tree using the parameters; the resulting tree will be cached
+      // by $this->buildTree()).
+      $this->menuPageTrees[$cid] = $this->buildTree($menu_name, $tree_parameters);
+    }
+    return $this->menuPageTrees[$cid];
+  }
+
+  /**
+   * Determines the required tree parameters used for the page menu tree.
+   *
+   * This method takes into account the active trail of the current page.
+   *
+   * @param string $menu_name
+   *   The menu name.
+   * @param int $max_depth
+   *   The maximum allowed depth of menus.
+   * @param bool $only_active_trail
+   *   If TRUE, just load level 0 plus the active trail, otherwise load the full
+   *   menu tree.
+   * @param bool $page_is_403
+   *   Is the current request happening on a 403 subrequest.
+   *
+   * @return array
+   */
+  protected function doBuildPageDataTreeParameters($menu_name, $max_depth, $only_active_trail, $page_is_403) {
+    $tree_parameters = array(
+      'min_depth' => 1,
+      'max_depth' => $max_depth,
+    );
+
+    // If this page is accessible to the current user, build the tree
+    // parameters accordingly.
+    if (!$page_is_403) {
+      $active_trail = $this->getActiveTrailIds($menu_name);
+      // The active trail contains more than only array(0 => 0).
+      if (count($active_trail) > 1) {
+        // If we are asked to build links for the active trail only,skip
+        // the entire 'expanded' handling.
+        if ($only_active_trail) {
+          $tree_parameters['only_active_trail'] = TRUE;
+        }
+      }
+      $parents = $active_trail;
+
+      if (!$only_active_trail) {
+        // Collect all the links set to be expanded, and then add all of
+        // their children to the list as well.
+        $parents = $this->treeStorage->getExpanded($menu_name, $parents);
+      }
+    }
+    else {
+      // If access is denied, we only show top-level links in menus.
+      $active_trail = array('' => '');
+      $parents = $active_trail;
+    }
+    $tree_parameters['expanded'] = $parents;
+    $tree_parameters['active_trail'] = $active_trail;
+    return $tree_parameters;
+  }
+
+  /**
+   * @TODO should this accept a menu link instance or just the ID?
+   */
+  public function buildAllData($menu_name, $link = NULL, $max_depth = NULL) {
+    $language_interface = $this->languageManager->getCurrentLanguage();
+
+    // Use ID as a flag for whether the data being loaded is for the whole
+    // tree.
+    $id = isset($link['id']) ? $link['id'] : '%';
+    // Generate a cache ID (cid) specific for this $menu_name, $link, $language,
+    // and depth.
+    $cid = 'links:' . $menu_name . ':all:' . $id . ':' . $language_interface->id . ':' . (int) $max_depth;
+    if (!isset($this->buildAllDataParameters[$cid])) {
+      $tree_parameters = array(
+        'min_depth' => 1,
+        'max_depth' => $max_depth,
+      );
+      if ($id != '%') {
+        // The tree is for a single item, so we need to match the values in
+        // of all the IDs on the path to root.
+        $tree_parameters['active_trail'] = $this->treeStorage->getMaterializedPathIds($id);
+        $tree_parameters['expanded'] = $tree_parameters['active_trail'];
+        // Include top-level links.
+        $tree_parameters['expanded'][''] = '';
+      }
+      $this->buildAllDataParameters[$cid] = $tree_parameters;
+    }
+    // Build the tree using the parameters; the resulting tree will be cached
+    // by buildTree().
+    return $this->buildTree($menu_name, $this->buildAllDataParameters[$cid]);
+  }
+
+  public function getChildLinks($id, $max_relative_depth = NULL) {
+    $links = array();
+    $definitions = $this->treeStorage->loadAllChildLinks($id, $max_relative_depth);
+    foreach($definitions as $id => $definition) {
+      $instance = $this->menuLinkTranslate($definition);
+      if ($instance) {
+        $links[$id] = $instance;
+      }
+    }
+    return $links;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildSubtree($id, $max_relative_depth = NULL) {
+    $subtree = $this->treeStorage->loadSubtree($id, $max_relative_depth);
+    if ($subtree) {
+      // Check access and instantiate. @todo rename these methods.
+      $instance = $this->menuLinkTranslate($subtree['definition']);
+      if ($instance) {
+        $subtree['link'] = $instance;
+        $this->checkAccess($subtree['below']);
+        return $subtree;
+      }
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildTree($menu_name, array $parameters = array()) {
+    $language_interface = $this->languageManager->getCurrentLanguage();
+
+    // Build the cache id; sort parents to prevent duplicate storage and remove
+    // default parameter values.
+    asort($parameters);
+    if (isset($parameters['expanded'])) {
+      sort($parameters['expanded']);
+    }
+    $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
+
+    // If we do not have this tree in the static cache, check cache.menu.
+    if (!isset($this->menuTree[$tree_cid])) {
+      $cache = $this->treeCacheBackend->get($tree_cid);
+      if ($cache && $cache->data) {
+        $this->menuTree[$tree_cid] = $cache->data;
+      }
+    }
+
+    if (!isset($this->menuTree[$tree_cid])) {
+      // Rebuild the links which are stored
+      $tree = $this->treeStorage->loadTree($menu_name, $parameters);
+      // Cache the data, if it is not already in the cache.
+      $this->treeCacheBackend->set($tree_cid, $tree, Cache::PERMANENT, array('menu' => $menu_name));
+      $this->menuTree[$tree_cid] = $tree;
+    }
+    else {
+      $tree = $this->menuTree[$tree_cid];
+    }
+    $this->checkAccess($tree);
+    return $tree;
+  }
+
+  /**
+   * Sorts the menu tree and recursively checks access for each item.
+   *
+   * @param array $tree
+   *   The menu tree you wish to operate on.
+   */
+  protected function checkAccess(&$tree) {
+    $new_tree = array();
+    foreach ($tree as $key => $v) {
+      $definition = $tree[$key]['definition'];
+      // Setting the definition here means it will be used by getDefinition()
+      // which is called by createInstance() from the factory.
+      $this->definitions[$definition['id']] = $definition;
+      $instance = $this->menuLinkTranslate($definition);
+      if ($instance) {
+        $tree[$key]['link'] = $instance;
+        if ($tree[$key]['below']) {
+          $this->checkAccess($tree[$key]['below']);
+        }
+        unset($tree[$key]['definition']);
+        // The weights are made a uniform 5 digits by adding 50000 as an offset.
+        // After $this->menuLinkTranslate(), $item['title'] has the localized
+        // link title. Adding the plugin id to the end of the index insures that
+        // it is unique.
+        $new_tree[(50000 + $instance->getWeight()) . ' ' . $instance->getTitle() . ' ' . $instance->getPluginId()] = $tree[$key];
+      }
+    }
+    // Sort siblings in the tree based on the weights and localized titles.
+    ksort($new_tree);
+    $tree = $new_tree;
+  }
+
+  /**
+   * Check access for the item and create an instance if it is accessible.
+   *
+   * @param array $definition
+   *   The menu link definition.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface|NULL
+   */
+  protected function menuLinkTranslate(array $definition) {
+
+    // url should only be populated for external links.
+    if (!empty($definition['url']) && empty($definition['route_name'])) {
+      $access = TRUE;
+    }
+    else {
+      $access = $this->accessManager->checkNamedRoute($definition['route_name'], $definition['route_parameters'], $this->account);
+    }
+    // For performance, don't instantiate a link the user can't access.
+    if ($access) {
+      return $this->createInstance($definition['id']);
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createLink($id, array $definition) {
+    // Add defaults and other stuff, so there is no requirement to specify
+    // everything.
+    $this->processDefinition($definition, $id);
+
+    // Store the new link in the tree and invalidate some caches.
+    $affected_menus = $this->treeStorage->save($definition);
+    Cache::invalidateTags(array('menu' => $affected_menus));
+    return $this->createInstance($id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateLink($id, array $new_definition_values, $persist = TRUE) {
+    $instance = $this->createInstance($id);
+    if ($instance) {
+      $new_definition_values['id'] = $id;
+      $changed_definition = $instance->updateLink($new_definition_values, $persist);
+      $affected_menus = $this->treeStorage->save($changed_definition);
+      $this->resetDefinitions();
+      Cache::invalidateTags(array('menu' => $affected_menus));
+    }
+    return $instance;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginForm(MenuLinkInterface $menu_link) {
+    $class_name = $menu_link->getFormClass();
+    /** @var \Drupal\Core\Menu\Form\MenuLinkFormInterface $form */
+    if (in_array('Drupal\Core\DependencyInjection\ContainerInjectionInterface', class_implements($class_name))) {
+      $form = $class_name::create(\Drupal::getContainer());
+    }
+    else {
+      $form = new $class_name();
+    }
+    $form->setMenuLinkInstance($menu_link);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParentSelectOptions($id = '', array $menus = array()) {
+    // @todo: Core allows you to replace the select element ... this is a sign
+    // that we might want to write a form element as well, which can be swapped.
+    if (empty($menus)) {
+      $menus = $this->getMenuOptions();
+    }
+
+    $options = array();
+    $depth_limit = $this->getParentDepthLimit($id);
+    foreach ($menus as $menu_name => $menu_title) {
+      $options[$menu_name . ':'] = '<' . $menu_title . '>';
+
+      $tree = $this->buildAllData($menu_name, NULL, $depth_limit);
+      $this->parentSelectOptionsTreeWalk($tree, $menu_name, '--', $options, $id, $depth_limit);
+    }
+    return $options;
+  }
+
+  public function getParentDepthLimit($id) {
+    $limit = $this->treeStorage->maxDepth() - 1;
+    if ($id) {
+      $limit = $limit - $this->treeStorage->findChildrenRelativeDepth($id);
+    }
+    return $limit;
+  }
+
+  protected function parentSelectOptionsTreeWalk(array $tree, $menu_name, $indent, &$options, $exclude, $depth_limit) {
+    foreach ($tree as $data) {
+      if ($data['depth'] > $depth_limit) {
+        // Don't iterate through any links on this level.
+        break;
+      }
+      /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+      $link = $data['link'];
+      if ($link->getPluginId() != $exclude) {
+        $title = $indent . ' ' . Unicode::truncate($link->getTitle(), 30, TRUE, FALSE);
+        if ($link->isHidden()) {
+          $title .= ' (' . t('disabled') . ')';
+        }
+        $options[$menu_name . ':' . $link->getPluginId()] = $title;
+        if ($data['below']) {
+          $this->parentSelectOptionsTreeWalk($data['below'], $menu_name, $indent . '--', $options, $exclude, $depth_limit);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMenuOptions(array $menu_names = NULL) {
+    $menus = $this->entityManager->getStorage('menu')->loadMultiple($menu_names);
+    $options = array();
+    foreach ($menus as $menu) {
+      $options[$menu->id()] = $menu->label();
+    }
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function menuNameExists($menu_name) {
+    $this->treeStorage->menuNameExists($menu_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetLink($id) {
+    $instance = $this->createInstance($id);
+    $affected_menus[$instance->getMenuName()] = $instance->getMenuName();
+    $new_instance = $this->resetInstance($instance);
+    $affected_menus[$new_instance->getMenuName()] = $new_instance->getMenuName();
+    Cache::invalidateTags(array('menu' => $affected_menus));
+    return $new_instance;
+  }
+
+  protected function resetInstance(MenuLinkInterface $instance) {
+    $id = $instance->getPluginId();
+
+    if (!$instance->isResetable()) {
+      throw new PluginException(String::format('Menu link %id is not resetable', array('%id' => $id)));
+    }
+    // Get the original data from disk, reset the override and re-save the menu
+    // tree for this link.
+    $definition = $this->getDefinitions()[$id];
+    $this->overrides->deleteOverride($id);
+    $this->resetDefinition($id, $definition);
+    $this->treeStorage->save($definition);
+    return $this->createInstance($id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetDefinitions() {
+    $this->definitions = array();
+  }
+
+  /**
+   * Resets the local definition cache for one plugin.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   * @param array $definition
+   */
+  protected function resetDefinition($id, $definition = NULL) {
+    $this->definitions[$id] = $definition;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTreeInterface.php b/core/lib/Drupal/Core/Menu/MenuLinkTreeInterface.php
new file mode 100644
index 0000000..10eda17
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTreeInterface.php
@@ -0,0 +1,312 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\MenuLinkTreeInterface.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+
+/**
+ * Provides an object which returns the available menu links.
+ */
+interface MenuLinkTreeInterface extends PluginManagerInterface {
+
+  /**
+   * Trigger discover save, and cleanup of static links
+   *
+   * @todo find a better name?
+   */
+  public function rebuild();
+
+  /**
+   * The maximum depth of tree that is supported.
+   *
+   * @return int
+   */
+  public function maxDepth();
+
+  /**
+   * Deletes or resets all links for a menu.
+   *
+   * @param string $menu_name
+   *   The name of the menu whose links will be deleted or reset.
+   */
+  public function deleteLinksInMenu($menu_name);
+
+  /**
+   * Deletes a single link from the menu tree.
+   *
+   * This should only be called when the link data has already been removed from
+   * any external storage.  This method will not attempt to persist the deletion
+   * except from the tree storage used by the plugin manager.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   * @param bool $persist
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   If the $id is not valid, existing, plugin ID or if the link cannot be
+   *   deleted.
+   */
+  public function deleteLink($id, $persist = TRUE);
+
+  /**
+   * Counts the total number of menu links.
+   *
+   * @param string $menu_name
+   *   (optional) The menu name to count by, defaults to NULL.
+   */
+  public function countMenuLinks($menu_name = NULL);
+
+  /**
+   * Load multiple plugin instances based on route.
+   *
+   * @param string $route_name
+   * @param array $route_parameters
+   * @param bool $include_hidden
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface[]
+   *   An array of instances keyed by ID.
+   */
+  public function loadLinksByRoute($route_name, array $route_parameters = array(), $include_hidden = FALSE);
+
+  /**
+   * Returns a menu tree ready to be rendered.
+   *
+   * The menu item's LI element is given one of the following classes:
+   * - expanded: The menu item is showing its submenu.
+   * - collapsed: The menu item has a submenu which is not shown.
+   * - leaf: The menu item has no submenu.
+   *
+   * @param array $tree
+   *   A data structure representing the tree as returned from menu_tree_data.
+   *
+   * @return array
+   *   A structured array to be rendered by drupal_render().
+   */
+  public function buildRenderTree($tree);
+
+  /**
+   * Gets the active trail IDs of the specified menu tree.
+   *
+   * @param string $menu_name
+   *   The menu name of the requested tree.
+   *
+   * @return array
+   *   An array containing the active trail: a list of plugin ids.
+   */
+  public function getActiveTrailIds($menu_name);
+
+  /**
+   * Gets the data structure for a named menu tree, based on the current page.
+   *
+   * Only visible links (hidden == 0) are returned in the data.
+   *
+   * @param string $menu_name
+   *   The named menu links to return.
+   * @param int $max_depth
+   *   (optional) The maximum depth of links to retrieve.
+   * @param bool $only_active_trail
+   *   (optional) Whether to only return the links in the active trail (TRUE)
+   *   instead of all links on every level of the menu link tree (FALSE).
+   *   Defaults to FALSE.
+   *
+   * @return array
+   *   An array of menu links, in the order they should be rendered. The array
+   *   is a list of associative arrays -- these have several keys:
+   *   - link: the menu link plugin instance
+   *   - below: the subtree below the link, or empty array. It has the same
+   *            structure as the top level array.
+   *   - depth:
+   *   - has_children: boolean. even if the below value may be empty the link
+   *                   may have children in the tree that are not shown. This
+   *                   is a hint for adding appropriate classes for theming.
+   *   - in_active_trail: boolean
+   */
+  public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE);
+
+  /**
+   * Gets the data structure representing a named menu tree.
+   *
+   * Since this can be the full tree including hidden items, the data returned
+   * may be used for generating an an admin interface or a select.
+   *
+   * @param string $menu_name
+   *   The named menu links to return
+   * @param array $link
+   *   A fully loaded menu link, or NULL. If a link is supplied, only the
+   *   path to root will be included in the returned tree - as if this link
+   *   represented the current page in a visible menu.
+   * @param int $max_depth
+   *   Optional maximum depth of links to retrieve. Typically useful if only one
+   *   or two levels of a sub tree are needed in conjunction with a non-NULL
+   *   $link, in which case $max_depth should be greater than $link['depth'].
+   *
+   * @return array
+   *   An tree of menu links in an array, in the order they should be rendered.
+   */
+  public function buildAllData($menu_name, $link = NULL, $max_depth = NULL);
+
+  /**
+   * Builds a menu tree, translates links, and checks access.
+   *
+   * @param string $menu_name
+   *   The name of the menu.
+   * @param array $parameters
+   *   (optional) An associative array of build parameters. Possible keys:
+   *   - expanded: An array of parent link ids to return only menu links that
+   *     are children of one of the ids in this list. If empty, the whole menu
+   *     tree is built, unless 'only_active_trail' is TRUE.
+   *   - active_trail: An array of ids, representing the coordinates of the
+   *     currently active menu link.
+   *   - only_active_trail: Whether to only return links that are in the active
+   *     trail. This option is ignored, if 'expanded' is non-empty.
+   *   - min_depth: The minimum depth of menu links in the resulting tree.
+   *     Defaults to 1, which is the default to build a whole tree for a menu
+   *     (excluding menu container itself).
+   *   - max_depth: The maximum depth of menu links in the resulting tree.
+   *   - conditions: An associative array of custom database select query
+   *     condition key/value pairs; see _menu_build_tree() for the actual query.
+   *
+   * @return array
+   *   A fully built menu tree.
+   */
+  public function buildTree($menu_name, array $parameters = array());
+
+  /**
+   * Returns a subtree starting with the passed in menu link plugin ID.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   * @param int $max_relative_depth
+   *   The maximum depth of child menu links relative to the passed in.
+   *
+   * @return array
+   */
+  public function buildSubtree($id, $max_relative_depth = NULL);
+
+  /**
+   * Loads all child links of a given menu link.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   *
+   * @param null $max_relative_depth
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface[]
+   *   An array of child links keyed by ID.
+   */
+  public function getChildLinks($id, $max_relative_depth = NULL);
+
+  /**
+   * Fetches a menu link which matches the route name, parameters and menu name.
+   * @param string $route_name
+   *   (optional) The route name
+   * @param array $route_parameters
+   * @param null $selected_menu
+   * @return mixed
+   */
+  public function menuLinkGetPreferred($route_name = NULL, array $route_parameters = array(), $selected_menu = NULL);
+
+  /**
+   * Adds a new link to the tree storage.
+   *
+   * Use this function in case you know there is no entry in the tree. This is
+   * the case if you don't use plugin definition to fill in the tree.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   * @param array $definition
+   *   The values of the link.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface
+   *   The updated menu link instance.
+   */
+  public function createLink($id, array $definition);
+
+  /**
+   * Updates the values for a menu link in the tree storage.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   * @param array $new_definition_values
+   *   The new values for the link definition. This will usually be just a
+   *   subset of the plugin definition.
+   * @param bool $persist
+   *   TRUE to also have the link instance itself persist the changed values
+   *   to any additional storage.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface
+   *   The updated menu link instance.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   If the $id is not valid, existing, plugin ID.
+   */
+  public function updateLink($id, array $new_definition_values, $persist = TRUE);
+
+  /**
+   * Resets the values for a menu link based on the values found by discovery.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface
+   *   The menu link instance after being reset.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   If the $id is not valid, existing, plugin ID or if the link cannot be
+   *   reset.
+   */
+  public function resetLink($id);
+
+  /**
+   * Get a form instance for editing a menu link plugin.
+   *
+   * @TODO Use the class resolver at some point.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link
+   *   The menu link.
+   *
+   * @return \Drupal\Core\Menu\Form\MenuLinkFormInterface
+   */
+  public function getPluginForm(MenuLinkInterface $menu_link);
+
+  /**
+   * Get the options for a select element to choose and menu and parent.
+   *
+   * @param string $id
+   *   Optional ID of a link plugin. This will exclude the link and its
+   *   children from the select options.
+   * @param array $menus
+   *   Optional array of menu names as keys and titles as values to limit
+   *   the select options.
+   *
+   * @return array
+   *   Keyed array where the keys are contain a menu name and parent ID and
+   *   the values are a menu name or link title indented by depth.
+   */
+  public function getParentSelectOptions($id = '', array $menus = array());
+
+  /**
+   * Get a list of menu names for use as options.
+   *
+   * @param array $menu_names
+   *   Optional array of menu names to limit the options, or NULL to load all.
+   *
+   * @return array
+   *   Keys are menu names (ids) values are the menu labels.
+   */
+  public function getMenuOptions(array $menu_names = NULL);
+
+  public function menuNameExists($menu_name);
+
+  public function getParentDepthLimit($id);
+
+  /**
+   * Resets any local definition cache. Used for testing.
+   */
+  public function resetDefinitions();
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuLinkTreeStorage.php
new file mode 100644
index 0000000..2397617
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTreeStorage.php
@@ -0,0 +1,1158 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\MenuLinkTreeStorage.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+
+class MenuLinkTreeStorage implements MenuLinkTreeStorageInterface {
+
+  /**
+   * The maximum depth of a menu links tree.
+   */
+  const MAX_DEPTH = 9;
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The URL generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGenerator
+   */
+  protected $urlGenerator;
+
+  /**
+   * The database table name.
+   *
+   * @var string
+   */
+  protected $table;
+
+  /**
+   * Additional database connection options to use in queries.
+   *
+   * @var array
+   */
+  protected $options = array();
+
+  /**
+   * Stores the menu tree used by the doBuildTree method, keyed by a cache ID.
+   *
+   * This cache ID is built using the $menu_name, the current language and
+   * some parameters passed into an entity query.
+   */
+  protected $menuTree;
+
+  /**
+   * List of serialized fields.
+   *
+   * @var array
+   */
+  protected $serializedFields;
+
+  /**
+   * List of plugin definition fields.
+   *
+   * @todo - inject this from the plugin manager?
+   *
+   * @var array
+   */
+  protected $definitionFields = array(
+    'menu_name',
+    'route_name',
+    'route_parameters',
+    'url',
+    'title',
+    'title_arguments',
+    'title_context',
+    'description',
+    'parent',
+    'weight',
+    'options',
+    'expanded',
+    'hidden',
+    'discovered',
+    'provider',
+    'metadata',
+    'class',
+    'form_class',
+    'id',
+  );
+
+  /**
+   * Constructs a new \Drupal\Core\Menu\MenuLinkTreeStorage.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   A Database connection to use for reading and writing configuration data.
+   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
+   *   The url generator.
+   * @param string $table
+   *   A database table name to store configuration data in.
+   * @param array $options
+   *   (optional) Any additional database connection options to use in queries.
+   */
+  public function __construct(Connection $connection, UrlGeneratorInterface $url_generator,  $table = 'menu_tree', array $options = array()) {
+    $this->connection = $connection;
+    $this->urlGenerator = $url_generator;
+    $this->table = $table;
+    $this->options = $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function maxDepth() {
+    return static::MAX_DEPTH;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rebuild(array $definitions) {
+    $links = array();
+    $children = array();
+    $top_links = array();
+    if ($definitions) {
+      foreach ($definitions as $id => $link) {
+        if (!empty($link['parent'])) {
+          $children[$link['parent']][$id] = $id;
+        }
+        else {
+          // A top level link - we need them to root our tree.
+          $top_links[$id] = $id;
+          $link['parent'] = '';
+        }
+        $links[$id] = $link;
+      }
+    }
+    foreach ($top_links as $id) {
+      $this->saveRecursive($id, $children, $links);
+    }
+    // Handle any children we didn't find starting from top-level links.
+    foreach ($children as $orphan_links) {
+      foreach ($orphan_links as $id) {
+        // Force it to the top level.
+        $links[$id]['parent'] = '';
+        $this->saveRecursive($id, $children, $links);
+      }
+    }
+    // Find any previously discovered menu links that no longer exist.
+    if ($definitions) {
+      $query = $this->connection->select($this->table, NULL, $this->options);
+      $query->addField($this->table, 'id');
+      $query->condition('discovered', 1);
+      $query->condition('id', array_keys($definitions), 'NOT IN');
+      $query->orderBy('depth', 'DESC');
+      $result = $query->execute()->fetchCol();
+    }
+    else {
+      $result = array();
+    }
+
+    // Remove all such items. Starting from those with the greatest depth will
+    // minimize the amount of re-parenting done by the menu link controller.
+    if ($result) {
+      $this->purgeMultiple($result);
+    }
+  }
+
+  /**
+   * Purges multiple menu links that no longer exist.
+   *
+   * @param array $ids
+   *   An array of menu link IDs.
+   * @param bool $prevent_reparenting
+   *   (optional) Disables the re-parenting logic from the deletion process.
+   *   Defaults to FALSE.
+   */
+  protected function purgeMultiple(array $ids, $prevent_reparenting = FALSE) {
+    if (!$prevent_reparenting) {
+      $loaded = $this->loadFullMultiple($ids);
+      foreach ($loaded as $id => $link) {
+        if ($link['has_children']) {
+          $children = $this->loadByProperties(array('parent' => $id));
+          foreach ($children as $child) {
+            $child['parent'] = $link['parent'];
+            $this->save($child);
+          }
+        }
+      }
+    }
+    $query = $this->connection->delete($this->table, $this->options);
+    $query->condition('id', $ids, 'IN');
+    $query->execute();
+  }
+
+  /**
+   * Execute a select query while making sure the database table exists.
+   *
+   * @param SelectInterface $query
+   *
+   * @return \Drupal\Core\Database\StatementInterface|null
+   *   A prepared statement, or NULL if the query is not valid.
+   *
+   * @throws \Exception
+   *   If the table could not be created or the database connection failed.
+   */
+  protected function safeExecuteSelect(SelectInterface $query) {
+    try {
+      return $query->execute();
+    }
+    catch (\Exception $e) {
+      // If there was an exception, try to create the table.
+      if ($this->ensureTableExists()) {
+        return $query->execute();
+      }
+      // Some other failure that we can not recover from.
+      throw $e;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $link) {
+    $original = $this->loadFull($link['id']);
+    // @todo - should we just return here if the links values match the original
+    //  values completely?.
+
+    $affected_menus = array();
+
+    $transaction = $this->connection->startTransaction();
+    try {
+      if ($original) {
+        $link['mlid'] = $original['mlid'];
+        $link['has_children'] = $original['has_children'];
+        $affected_menus[$original['menu_name']] = $original['menu_name'];
+      }
+      else {
+        // Generate a new mlid.
+        $options = array('return' => Database::RETURN_INSERT_ID) + $this->options;
+        $link['mlid'] = $this->connection->insert($this->table, $options)
+          ->fields(array('id' => $link['id'], 'menu_name' => $link['menu_name']))
+          ->execute();
+      }
+      $fields = $this->preSave($link, $original);
+      // We may be moving the link to a new menu.
+      $affected_menus[$fields['menu_name']] = $fields['menu_name'];
+      $query = $this->connection->update($this->table, $this->options);
+      $query->condition('mlid', $link['mlid']);
+      $query->fields($fields)
+        ->execute();
+      if ($original) {
+        $this->updateParentalStatus($original);
+      }
+      $this->updateParentalStatus($link);
+      // Ignore slave server temporarily.
+      db_ignore_slave();
+    }
+    catch (\Exception $e) {
+      $transaction->rollback();
+      throw $e;
+    }
+    return $affected_menus;
+  }
+
+  /**
+   * Using the link definition, but up all the fields needed for database save.
+   */
+  protected function preSave(&$link, $original) {
+    static $schema_fields, $schema_defaults;
+    if (empty($schema_fields)) {
+      $schema = static::schemaDefinition();
+      $schema_fields = $schema['fields'];
+      foreach ($schema_fields as $name => $spec) {
+        if (isset($spec['default'])) {
+          $schema_defaults[$name] = $spec['default'];
+        }
+      }
+    }
+
+    // Try to find a parent link. If found, assign it and derive its menu.
+    $parent = $this->findParent($link, $original);
+    if ($parent) {
+      $link['parent'] = $parent['id'];
+      $link['menu_name'] = $parent['menu_name'];
+    }
+    else {
+      $link['parent'] = '';
+    }
+
+    // If no corresponding parent link was found, move the link to the top-level.
+    foreach ($schema_defaults as $name => $default) {
+      if (!isset($link[$name])) {
+        $link[$name] = $default;
+      }
+    }
+    $fields = array_intersect_key($link, $schema_fields);
+    asort($fields['route_parameters']);
+    // Since this will be urlencoded, it's safe to store and match against a
+    // text field.
+    $fields['route_param_key'] = $fields['route_parameters'] ? UrlHelper::buildQuery($fields['route_parameters']) : '';
+
+    foreach ($this->serializedFields() as $name) {
+      $fields[$name] = serialize($fields[$name]);
+    }
+
+    // Directly fill parents for top-level links.
+    if (empty($link['parent'])) {
+      $fields['p1'] = $link['mlid'];
+      for ($i = 2; $i <= $this->maxDepth(); $i++) {
+        $fields["p$i"] = 0;
+      }
+      $fields['depth'] = 1;
+    }
+    // Otherwise, ensure that this link's depth is not beyond the maximum depth
+    // and fill parents based on the parent link.
+    else {
+      // @todo - we want to also check $original['has_children'] here, but that
+      //  will be 0 even if there are children if those are hidden. has_children
+      //  is really just the rendering hint. So, we either need to define
+      //  another column (has_any_children), or always do the extra query here.
+      if ($original) {
+        $limit = $this->maxDepth() - $this->doFindChildrenRelativeDepth($original) - 1;
+      }
+      else {
+        $limit = $this->maxDepth() - 1;
+      }
+      if ($parent['depth'] > $limit) {
+        throw new PluginException(sprintf('The link with ID %s or its children exceeded the maximum depth of %d', $link['id'], $this->maxDepth()));
+      }
+      $this->setParents($fields, $parent);
+    }
+
+    // Need to check both parent and menu_name, since parent can be NULL in any menu.
+    if ($original && ($link['parent'] != $original['parent'] || $link['menu_name'] != $original['menu_name'])) {
+      $this->moveChildren($fields, $original);
+    }
+    // We needed the mlid above, but not in the update query.
+    unset($fields['mlid']);
+
+    // Cast booleans to int, if needed.
+    $fields['hidden'] = (int) $fields['hidden'];
+    $fields['expanded'] = (int) $fields['expanded'];
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($id) {
+    // Children get re-attached to the menu link's parent.
+    $item = $this->loadFull($id);
+    // It's possible the link is already deleted.
+    if ($item) {
+      $parent = $item['parent'];
+      $children = $this->loadByProperties(array('parent' => $id));
+      foreach ($children as $child) {
+        $child['parent'] = $parent;
+        $this->save($child);
+      }
+
+      $this->connection->delete($this->table, $this->options)
+        ->condition('id', $id)
+        ->execute();
+
+      $this->updateParentalStatus($item);
+    }
+  }
+
+  /**
+   * @TODO Figure out whether public methods should always pass along the
+   * menu link instance or never ...
+   */
+  public function findChildrenRelativeDepth($id) {
+    return $this->doFindChildrenRelativeDepth($this->loadFull($id));
+  }
+
+  /**
+   * Find the relative depth of this link's deepest child.
+   */
+  protected function doFindChildrenRelativeDepth($original) {
+    $query = $this->connection->select($this->table, $this->options);
+    $query->addField($this->table, 'depth');
+    $query->condition('menu_name', $original['menu_name']);
+    $query->orderBy('depth', 'DESC');
+    $query->range(0, 1);
+
+    for ($i = 1; $i <= static::MAX_DEPTH && $original["p$i"]; $i++) {
+      $query->condition("p$i", $original["p$i"]);
+    }
+
+    $max_depth = $this->safeExecuteSelect($query)->fetchField();
+
+    return ($max_depth > $original['depth']) ? $max_depth - $original['depth'] : 0;
+  }
+
+  /**
+   * Set the materialized path field values based on the parent.
+   */
+  protected function setParents(&$fields, $parent) {
+    $fields['depth'] = $parent['depth'] + 1;
+    $i = 1;
+    while ($i < $fields['depth']) {
+      $p = 'p' . $i++;
+      $fields[$p] = $parent[$p];
+    }
+    $p = 'p' . $i++;
+    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
+    $fields[$p] = $fields['mlid'];
+    while ($i <= static::MAX_DEPTH) {
+      $p = 'p' . $i++;
+      $fields[$p] = 0;
+    }
+  }
+
+  /**
+   * Using the query field values and original values, move the link's children.
+   */
+  protected function moveChildren($fields, $original) {
+    $query = $this->connection->update($this->table, $this->options);
+
+    $query->fields(array('menu_name' => $fields['menu_name']));
+
+    $expressions = array();
+    for ($i = 1; $i <= $fields['depth']; $i++) {
+      $expressions[] = array("p$i", ":p_$i", array(":p_$i" => $fields["p$i"]));
+    }
+    $j = $original['depth'] + 1;
+    while ($i <= $this->maxDepth() && $j <= $this->maxDepth()) {
+      $expressions[] = array('p' . $i++, 'p' . $j++, array());
+    }
+    while ($i <= $this->maxDepth()) {
+      $expressions[] = array('p' . $i++, 0, array());
+    }
+
+    $shift = $fields['depth'] - $original['depth'];
+    if ($shift > 0) {
+      // The order of expressions must be reversed so the new values don't
+      // overwrite the old ones before they can be used because "Single-table
+      // UPDATE assignments are generally evaluated from left to right"
+      // @see http://dev.mysql.com/doc/refman/5.0/en/update.html
+      $expressions = array_reverse($expressions);
+    }
+    foreach ($expressions as $expression) {
+      $query->expression($expression[0], $expression[1], $expression[2]);
+    }
+
+    $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
+    $query->condition('menu_name', $original['menu_name']);
+
+    for ($i = 1; $i <= $this->maxDepth() && $original["p$i"]; $i++) {
+      $query->condition("p$i", $original["p$i"]);
+    }
+
+    $query->execute();
+  }
+
+  /**
+   * Load the parent definition if it exists.
+   */
+  protected function findParent($link, $original) {
+    $parent = FALSE;
+
+    // This item is explicitly top-level, skip the rest of the parenting.
+    if (isset($link['parent']) && empty($link['parent'])) {
+      return $parent;
+    }
+
+    // If we have a parent link ID, try to use that.
+    $candidates = array();
+    if (isset($link['parent'])) {
+      $candidates[] = $link['parent'];
+    }
+    elseif ($original['parent'] && $link['menu_name'] == $original['menu_name']) {
+      $candidates[] = $original['parent'];
+    }
+
+    // Else, if we have a link hierarchy try to find a valid parent in there.
+    // @todo - why does this make sense to do at all?
+
+    foreach ($candidates as $id) {
+      $parent = $this->loadFull($id);
+      if ($parent) {
+        break;
+      }
+    }
+    return $parent;
+  }
+
+  /**
+   * Set the has_children flag for the link's parent if it has visible children.
+   *
+   * @param array $link
+   */
+  protected function updateParentalStatus(array $link) {
+    // If parent is empty, there is nothing to update.
+    if (!empty($link['parent'])) {
+      // Check if at least one visible child exists in the table.
+      $query = $this->connection->select($this->table, $this->options);
+      $query->addExpression('1');
+      $query->range(0, 1);
+      $query
+        ->condition('menu_name', $link['menu_name'])
+        ->condition('parent', $link['parent'])
+        ->condition('hidden', 0);
+
+      $parent_has_children = ((bool) $query->execute()->fetchField()) ? 1 : 0;
+      $this->connection->update($this->table, $this->options)
+        ->fields(array('has_children' => $parent_has_children))
+        ->condition('id', $link['parent'])
+        ->execute();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadByProperties(array $properties) {
+    // @todo - only allow loading by plugin definition properties.
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table, $this->definitionFields());
+    foreach ($properties as $name => $value) {
+      $query->condition($name, $value);
+    }
+    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    foreach ($loaded as &$link) {
+      foreach ($this->serializedFields() as $name) {
+        $link[$name] = unserialize($link[$name]);
+      }
+    }
+    return $loaded;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadByRoute($route_name, array $route_parameters = array(), $include_hidden = FALSE) {
+    asort($route_parameters);
+    // Since this will be urlencoded, it's safe to store and match against a
+    // text field.
+    // @todo - does this make more sense than using the system path?
+    $param_key = $route_parameters ? UrlHelper::buildQuery($route_parameters) : '';
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table, $this->definitionFields());
+    $query->condition('route_name', $route_name);
+    $query->condition('route_param_key', $param_key);
+    if (!$include_hidden) {
+      $query->condition('hidden', 0);
+    }
+    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    foreach ($loaded as &$link) {
+      foreach ($this->serializedFields() as $name) {
+        $link[$name] = unserialize($link[$name]);
+      }
+    }
+    return $loaded;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(array $ids) {
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table, $this->definitionFields());
+    $query->condition('id', $ids, 'IN');
+    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    foreach ($loaded as &$link) {
+      foreach ($this->serializedFields() as $name) {
+        $link[$name] = unserialize($link[$name]);
+      }
+    }
+    return $loaded;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    $loaded = $this->loadMultiple(array($id));
+    return isset($loaded[$id]) ? $loaded[$id] : FALSE;
+  }
+
+  /**
+   * Load all table fields, not just those that are in the plugin definition.
+   */
+  protected function loadFull($id) {
+    $loaded = $this->loadFullMultiple(array($id));
+    return isset($loaded[$id]) ? $loaded[$id] : FALSE;
+  }
+
+  protected  function loadFullMultiple(array $ids) {
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table);
+    $query->condition('id', $ids, 'IN');
+    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    foreach ($loaded as &$link) {
+      foreach ($this->serializedFields() as $name) {
+        $link[$name] = unserialize($link[$name]);
+      }
+    }
+    return $loaded;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMaterializedPathIds($id) {
+    $subquery = $this->connection->select($this->table, $this->options);
+    // @todo: consider making this dynamic based on static::MAX_DEPTH
+    //   or from the schema if that is generated using static::MAX_DEPTH.
+    $subquery->fields($this->table, array('p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'));
+    $subquery->condition('id', $id);
+    $result = current($subquery->execute()->fetchAll(\PDO::FETCH_ASSOC));
+    $ids = array_filter($result);
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table, array('id'));
+    $query->orderBy('depth', 'ASC');
+    $query->condition('mlid', $ids, 'IN');
+    // @todo: cache this result in memory if we find it's being used more
+    //   than once per page load.
+    return $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExpanded($menu_name, array $parents) {
+    // @todo go back to tracking in state or some other way
+    // which menus have expanded links?
+    do {
+      $query = $this->connection->select($this->table, $this->options);
+      $query->fields($this->table, array('id'));
+      $query->condition('menu_name', $menu_name);
+      $query->condition('expanded', 1);
+      $query->condition('has_children', 1);
+      $query->condition('hidden', 0);
+      $query->condition('parent', $parents, 'IN');
+      $query->condition('id', $parents, 'NOT IN');
+      $result = $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+      $parents += $result;
+    } while (!empty($result));
+    return $parents;
+  }
+
+  /**
+   * Saves menu links recursively.
+   */
+  protected function saveRecursive($id, &$children, &$links) {
+
+    if (!empty($links[$id]['parent']) && empty($links[$links[$id]['parent']])) {
+      // Invalid parent ID, so remove it
+      $links[$id]['parent'] = '';
+    }
+    $this->save($links[$id]);
+
+    if (!empty($children[$id])) {
+      foreach ($children[$id] as $next_id) {
+        $this->saveRecursive($next_id, $children, $links);
+      }
+    }
+    // Remove processed link names so we can find stragglers.
+    unset($children[$id]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadTree($menu_name, array $parameters = array()) {
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table);
+    for ($i = 1; $i <= $this->maxDepth(); $i++) {
+      $query->orderBy('p' . $i, 'ASC');
+    }
+
+    $query->condition('menu_name', $menu_name);
+
+    if (!empty($parameters['expanded'])) {
+      $query->condition('parent', $parameters['expanded'], 'IN');
+    }
+    elseif (!empty($parameters['only_active_trail'])) {
+      $query->condition('id', $parameters['active_trail'], 'IN');
+    }
+    $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : NULL);
+    if ($min_depth) {
+      $query->condition('depth', $min_depth, '>=');
+    }
+    if (isset($parameters['max_depth'])) {
+      $query->condition('depth', $parameters['max_depth'], '<=');
+    }
+    // Add custom query conditions, if any were passed.
+    if (!empty($parameters['conditions'])) {
+      // Only allow conditions that are testing definition fields.
+      $parameters['conditions'] = array_intersect_key($parameters['conditions'], array_flip($this->definitionFields()));
+      foreach ($parameters['conditions'] as $column => $value) {
+        $query->condition($column, $value);
+      }
+    }
+    $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
+    $links = $this->safeExecuteSelect($query)->fetchAll(\PDO::FETCH_ASSOC);
+    if (!isset($min_depth)) {
+      $first_link = reset($links);
+      if ($first_link) {
+        $min_depth = $first_link['depth'];
+      }
+    }
+    $tree = $this->doBuildTreeData($links, $active_trail, $min_depth);
+    return $tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadSubtree($id, $max_relative_depth = NULL) {
+    $tree = array();
+    $root = $this->loadFull($id);
+    if (!$root) {
+      return $tree;
+    }
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table, $this->definitionFields());
+    for ($i = 1; $i <= $this->maxDepth(); $i++) {
+      $query->orderBy('p' . $i, 'ASC');
+    }
+    $query->condition('hidden', 0);
+    $query->condition('menu_name', $root['menu_name']);
+    for ($i = 1; $i <= $root['depth']; $i++) {
+      $query->condition("p$i", $root["p$i"]);
+    }
+    if (!empty($max_relative_depth)) {
+      $query->condition('depth', (int) $root['depth'] + $max_relative_depth, '<=');
+    }
+    $links = $this->safeExecuteSelect($query)->fetchAll(\PDO::FETCH_ASSOC);
+    $tree = $this->doBuildTreeData($links, array(), $root['depth']);
+    $subtree = current($tree);
+    return $subtree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function menuNameExists($menu_name) {
+    $query = $this->connection->select($this->table, $this->options);
+    $query->addField($this->table, 'mlid');
+    $query->condition('menu_name', $menu_name);
+    $query->range(0, 1);
+    return (bool) $this->safeExecuteSelect($query);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMenuNames() {
+    $query = $this->connection->select($this->table, $this->options);
+    $query->addField($this->table, 'menu_name');
+    $query->distinct();
+    return $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function countMenuLinks($menu_name = NULL) {
+    $query = $this->connection->select($this->table, $this->options);
+    if ($menu_name) {
+      $query->condition('menu_name', $menu_name);
+    }
+    return $this->safeExecuteSelect($query->countQuery())->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadAllChildLinks($id, $max_relative_depth = NULL) {
+    $tree = array();
+    $root = $this->loadFull($id);
+    if (!$root || $root['depth'] == $this->maxDepth()) {
+      return $tree;
+    }
+    $query = $this->connection->select($this->table, $this->options);
+    $query->fields($this->table, $this->definitionFields());
+    $query->condition('hidden', 0);
+    $query->condition('menu_name', $root['menu_name']);
+    for ($i = 1; $i <= $root['depth']; $i++) {
+      $query->condition("p$i", $root["p$i"]);
+    }
+    // The next p column should not be empty. This excludes the root link.
+    $query->condition("p$i", 0, '>');
+    if (!empty($max_relative_depth)) {
+      $query->condition('depth', (int) $root['depth'] + $max_relative_depth, '<=');
+    }
+    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    foreach ($loaded as &$link) {
+      foreach ($this->serializedFields() as $name) {
+        $link[$name] = unserialize($link[$name]);
+      }
+    }
+    return $loaded;
+  }
+
+  /**
+   * Prepares the data for calling $this->treeDataRecursive().
+   */
+  protected function doBuildTreeData(array $links, array $parents = array(), $depth = 1) {
+    // Reverse the array so we can use the more efficient array_pop() function.
+    $links = array_reverse($links);
+    return $this->treeDataRecursive($links, $parents, $depth);
+  }
+
+  /**
+   * Builds the data representing a menu tree.
+   *
+   * The function is a bit complex because the rendering of a link depends on
+   * the next menu link.
+   *
+   * @param array $links
+   *   A flat array of menu links that are part of the menu. Each array element
+   *   is an associative array of information about the menu link, containing
+   *   the fields from the {menu_links} table, and optionally additional
+   *   information from the {menu_router} table, if the menu item appears in
+   *   both tables. This array must be ordered depth-first.
+   *   See _menu_build_tree() for a sample query.
+   * @param array $parents
+   *   An array of the menu link ID values that are in the path from the current
+   *   page to the root of the menu tree.
+   * @param int $depth
+   *   The minimum depth to include in the returned menu tree.
+   *
+   * @return array
+   */
+  protected function treeDataRecursive(&$links, $parents, $depth) {
+    $tree = array();
+    while ($item = array_pop($links)) {
+      // We need to determine if we're on the path to root so we can later build
+      // the correct active trail.
+      foreach ($this->serializedFields() as $name) {
+        $item[$name] = unserialize($item[$name]);
+      }
+      // Add the current link to the tree.
+      $tree[$item['id']] = array(
+        'definition' => array_intersect_key($item, array_flip($this->definitionFields())),
+        'has_children' => $item['has_children'],
+        'in_active_trail' => in_array($item['id'], $parents),
+        'below' => array(),
+        'depth' => $item['depth'],
+      );
+      for ($i = 1; $i <= $this->maxDepth(); $i++) {
+        $tree[$item['id']]['p' . $i] = $item['p' . $i];
+      }
+      // Look ahead to the next link, but leave it on the array so it's
+      // available to other recursive function calls if we return or build a
+      // sub-tree.
+      $next = end($links);
+      // Check whether the next link is the first in a new sub-tree.
+      if ($next && $next['depth'] > $depth) {
+        // Recursively call doBuildTreeData to build the sub-tree.
+        $tree[$item['id']]['below'] = $this->treeDataRecursive($links, $parents, $next['depth']);
+        // Fetch next link after filling the sub-tree.
+        $next = end($links);
+      }
+      // Determine if we should exit the loop and return.
+      if (!$next || $next['depth'] < $depth) {
+        break;
+      }
+    }
+    return $tree;
+  }
+
+  /**
+   * Check if the tree table exists and create it if not.
+   *
+   * @return bool
+   *   TRUE if the table was created, FALSE otherwise.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   If a database error occurs.
+   */
+  protected function ensureTableExists() {
+    try {
+      if (!$this->connection->schema()->tableExists($this->table)) {
+        $this->connection->schema()->createTable($this->table, static::schemaDefinition());
+        return TRUE;
+      }
+    }
+      // If another process has already created the config table, attempting to
+      // recreate it will throw an exception. In this case just catch the
+      // exception and do nothing.
+    catch (SchemaObjectExistsException $e) {
+      return TRUE;
+    }
+    catch (\Exception $e) {
+      throw new PluginException($e->getMessage(), NULL, $e);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Helper function to determine serialized fields.
+   */
+  protected function serializedFields() {
+    // For now, build the list from the schema since it's in active development.
+    if (empty($this->serializedFields)) {
+      $schema = static::schemaDefinition();
+      foreach ($schema['fields'] as $name => $field) {
+        if (!empty($field['serialize'])) {
+          $this->serializedFields[] = $name;
+        }
+      }
+    }
+    return $this->serializedFields;
+  }
+
+  /**
+   * Helper function to determine fields that are part of the plugin definition.
+   */
+  protected function definitionFields() {
+    return $this->definitionFields;
+  }
+
+  /**
+   * Defines the schema for the tree table.
+   */
+  protected static function schemaDefinition() {
+    $schema = array(
+      'description' => 'Contains the menu tree hierarchy.',
+      'fields' => array(
+        'menu_name' => array(
+          'description' => "The menu name. All links with the same menu name (such as 'tools') are part of the same menu.",
+          'type' => 'varchar',
+          'length' => 32,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'mlid' => array(
+          'description' => 'The menu link ID (mlid) is the integer primary key.',
+          'type' => 'serial',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+        'id' => array(
+          'description' => 'Unique machine name: the plugin ID.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+        ),
+        'parent' => array(
+          'description' => 'The plugin ID for the parent of this link.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'route_name' => array(
+          'description' => 'The machine name of a defined Symfony Route this menu item represents.',
+          'type' => 'varchar',
+          'length' => 255,
+        ),
+        'route_param_key' => array(
+          'description' => 'An encoded string of route parameters for loading by route.',
+          'type' => 'varchar',
+          'length' => 255,
+        ),
+        'route_parameters' => array(
+          'description' => 'Serialized array of route parameters of this menu link.',
+          'type' => 'blob',
+          'size' => 'big',
+          'not null' => FALSE,
+          'serialize' => TRUE,
+        ),
+        'url' => array(
+          'description' => 'The external path this link points to (when not using a route).',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'title' => array(
+          'description' => 'The text displayed for the link.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'title_arguments' => array(
+          'description' => 'A serialized array of arguments to be passed to t() (if this plugin uses it).',
+          'type' => 'blob',
+          'size' => 'big',
+          'not null' => FALSE,
+          'serialize' => TRUE,
+        ),
+        'title_context' => array(
+          'description' => 'The translation context for the link title.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'description' => array(
+          'description' => 'The description of this link - used for admin pages and title attribute.',
+          'type' => 'text',
+          'not null' => FALSE,
+        ),
+        'class' => array(
+          'description' => 'The class for this link plugin.',
+          'type' => 'text',
+          'not null' => FALSE,
+        ),
+        'options' => array(
+          'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
+          'type' => 'blob',
+          'size' => 'big',
+          'not null' => FALSE,
+          'serialize' => TRUE,
+        ),
+        'provider' => array(
+          'description' => 'The name of the module that generated this link.',
+          'type' => 'varchar',
+          'length' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
+          'not null' => TRUE,
+          'default' => 'system',
+        ),
+        'hidden' => array(
+          'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, 0 = a normal, visible link)',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small',
+        ),
+        'discovered' => array(
+          'description' => 'A flag for whether the link was discovered, so can be purged on rebuild',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small',
+        ),
+        'expanded' => array(
+          'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small',
+        ),
+        'weight' => array(
+          'description' => 'Link weight among links in the same menu at the same depth.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'metadata' => array(
+          'description' => 'A serialized array of data that may be used by the plugin instance.',
+          'type' => 'blob',
+          'size' => 'big',
+          'not null' => FALSE,
+          'serialize' => TRUE,
+        ),
+        'has_children' => array(
+          'description' => 'Flag indicating whether any non-hidden links have this link as a parent (1 = children exist, 0 = no children).',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small',
+        ),
+        'depth' => array(
+          'description' => 'The depth relative to the top level. A link with empty parent will have depth == 1.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small',
+        ),
+        'p1' => array(
+          'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the parent link mlid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p2' => array(
+          'description' => 'The second mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p3' => array(
+          'description' => 'The third mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p4' => array(
+          'description' => 'The fourth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p5' => array(
+          'description' => 'The fifth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p6' => array(
+          'description' => 'The sixth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p7' => array(
+          'description' => 'The seventh mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p8' => array(
+          'description' => 'The eighth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'p9' => array(
+          'description' => 'The ninth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'form_class' => array(
+          'description' => 'meh',
+          'type' => 'varchar',
+          'length' => 255,
+        ),
+      ),
+      'indexes' => array(
+        'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+        // @todo test this index for effectiveness.
+        'menu_parent_expand_child' => array('menu_name', 'expanded', 'has_children', array('parent', 16)),
+        'route_values' => array(array('route_name', 32), array('route_param_key', 16)),
+      ),
+      'primary key' => array('mlid'),
+      'unique keys' => array(
+        'id' => array('id'),
+      ),
+    );
+
+    return $schema;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTreeStorageInterface.php b/core/lib/Drupal/Core/Menu/MenuLinkTreeStorageInterface.php
new file mode 100644
index 0000000..e1cafd9
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTreeStorageInterface.php
@@ -0,0 +1,189 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\MenuLinkTreeStorageInterface.
+ */
+
+namespace Drupal\Core\Menu;
+
+interface MenuLinkTreeStorageInterface {
+
+  /**
+   * The maximum depth of tree the storage implementation supports.
+   *
+   * @return int
+   */
+  public function maxDepth();
+
+  /**
+   * @param array $definitions
+   *
+   * @todo give this a better name.
+   */
+  public function rebuild(array $definitions);
+
+  /**
+   * Load a plugin definition from the storage.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   * @return array|FALSE
+   *   Menu Link definition
+   */
+  public function load($id);
+
+  /**
+   * Load multiple plugin definitions from the storage.
+   *
+   * @param array $ids
+   *  An array of plugin IDs.
+   *
+   * @return array
+   *   Array of menu Link definitions
+   */
+  public function loadMultiple(array $ids);
+
+  /**
+   * Load multiple plugin definitions from the storage based on properties.
+   *
+   * @param array $properties
+   * @return array
+   *   Array of menu link definitions
+   */
+  public function loadByProperties(array $properties);
+
+  /**
+   * Load multiple plugin definitions from the storage based on route.
+   *
+   * @param string $route_name
+   * @param array $route_parameters
+   * @param bool $include_hidden
+   *
+   * @return array
+   *  Array of menu link definitions keyed by ID.
+   */
+  public function loadByRoute($route_name, array $route_parameters = array(), $include_hidden = FALSE);
+
+  /**
+   * Save a plugin definition to the storage.
+   *
+   * @param array $definition
+   *   A definition for a \Drupal\Core\Menu\MenuLinkInterface plugin.
+   *
+   * @return array
+   *   The names of the menus affected by the save operation (1 or 2).
+   *
+   * @throws \Exception
+   *   If the storage back-end does not exist and could not be created.
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   If the definition is invalid - for example, if the specified parent
+   *   would cause the links children to be moved to greater than the maximum
+   *   depth.
+   */
+  public function save(array $definition);
+
+  /**
+   * Deletes a menu plugin from the storage.
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   */
+  public function delete($id);
+
+  /**
+   * Loads a menu tree from the storage.
+   *
+   * This function may be used build the data for a menu tree only, for example
+   * to further massage the data manually before further processing happens.
+   * MenuLinkTree::checkAccess() needs to be invoked afterwards.
+   *
+   * The tree order is maintained using an optimized algorithm, for example by
+   * storing each parent in an individual field, see
+   * http://drupal.org/node/141866 for more details. However, any details
+   * of the storage should not be relied upon since it may be swapped with
+   * a different implementation.
+   *
+   * @param string $menu_name
+   *   The name of the menu.
+   * @param array $parameters
+   *   (optional) An associative array of build parameters. Possible keys:
+   *   - expanded: An array of parent plugin ids to return only menu links that
+   *     are children of one of the ids in this list. If empty, the whole menu
+   *     tree is built, unless 'only_active_trail' is TRUE.
+   *   - active_trail: An array of ids, representing the coordinates of the
+   *     currently active menu link.
+   *   - only_active_trail: Whether to only return links that are in the active
+   *     trail. This option is ignored if 'expanded' is non-empty.
+   *   - min_depth: The minimum depth of menu links in the resulting tree.
+   *     Defaults to 1, which is the default to build a whole tree for a menu
+   *     (excluding menu container itself).
+   *   - max_depth: The maximum depth of menu links in the resulting tree.
+   *   - conditions: An associative array of custom condition key/value pairs
+   *     to restrict the links loaded. Each key must be one of the keys
+   *     in the plugin definition.
+   *
+   * @return array
+   *   A fully built menu tree.
+   */
+  public function loadTree($menu_name, array $parameters = array());
+
+  /**
+   * Load all the visible links that are below the given ID.
+   *
+   * The returned links are not ordered, and visible children will be
+   * included even if they have a hidden parent or ancestor so would not
+   * normally appear in a rendered tree.
+   *
+   * @param string $id
+   * @param int $max_relative_depth
+   *
+   * @return array
+   *   Array of link definitions, keyed by ID.
+   */
+  public function loadAllChildLinks($id, $max_relative_depth = NULL);
+
+  /**
+   * Load a subtree rooted by the given ID.
+   *
+   * The returned links are structured like those from loadTree().
+   *
+   * @param string $id
+   *   The menu link plugin ID.
+   * @param int $max_relative_depth
+   *   The maximum depth of child menu links relative to the passed in.
+   *
+   * @return array
+   */
+  public function loadSubtree($id, $max_relative_depth = NULL);
+
+  /**
+   * @param $id
+   * @return array
+   *   An array of plugin IDs that represents the path from this plugin ID
+   *   to the root of the tree.
+   */
+  public function getMaterializedPathIds($id);
+
+  /**
+   * @param string $menu_name
+   * @param array $parents
+   * @return array
+   *   The menu link ID that are flagged as expanded in this menu.
+   */
+  public function getExpanded($menu_name, array $parents);
+
+  public function findChildrenRelativeDepth($id);
+
+  public function menuNameExists($menu_name);
+
+  public function getMenuNames();
+
+  /**
+   * Counts the total amount of menu links.
+   *
+   * @param string $menu_name
+   *   (optional) The menu name to count by, defaults to NULL.
+   */
+  public function countMenuLinks($menu_name = NULL);
+}
diff --git a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php
new file mode 100644
index 0000000..35832d0
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\StaticMenuLinkOverrides.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+
+/**
+ * Implementation of the menu link override using a config file.
+ */
+class StaticMenuLinkOverrides implements StaticMenuLinkOverridesInterface {
+
+  /**
+   * The config name used to store the overrides.
+   *
+   * @var string
+   */
+  protected $configName = 'menu_link.static.overrides';
+
+  /**
+   * The menu link overrides config object.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * The config factory object.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  public function __construct(ConfigFactoryInterface $config_factory) {
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * Helper function to get the config object.
+   *
+   * Since this service is injected into all static menu link objects, but
+   * only used when updating one, avoid actually loading the config when it's
+   * not needed.
+   */
+  protected function getConfig() {
+    if (empty($this->config)) {
+      $this->config = $this->configFactory->get($this->configName);
+    }
+    return $this->config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadOverride($id) {
+    $all_overrides = $this->getConfig()->get('definitions');
+    return $id && isset($all_overrides[$id]) ? $all_overrides[$id] : array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteMultipleOverrides(array $ids) {
+    $all_overrides = $this->getConfig()->get('definitions');
+    $save = FALSE;
+    foreach ($ids as $id) {
+      if (isset($all_overrides[$id])) {
+        unset($all_overrides[$id]);
+        $save = TRUE;
+      }
+    }
+    if ($save) {
+      $this->getConfig()->set('definitions', $all_overrides)->save();
+    }
+    return $save;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteOverride($id) {
+    return $this->deleteMultipleOverrides(array($id));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultipleOverrides(array $ids) {
+    $result = array();
+    if ($ids) {
+      $all_overrides = $this->getConfig()->get('definitions') ?: array();
+      $id_keys = array_flip($ids);
+      $result = array_intersect_key($all_overrides, $id_keys);
+    }
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveOverride($id, array $definition) {
+    // Remove unexpected keys.
+    $definition = array_intersect_key($definition, array('menu_name' => 1, 'parent' => 1, 'weight' => 1, 'expanded' => 1, 'hidden' => 1));
+    if ($definition) {
+      $all_overrides = $this->getConfig()->get('definitions');
+      // Combine with any existing data.
+      $all_overrides[$id] = $definition + $this->loadOverride($id);
+      $this->getConfig()->set('definitions', $all_overrides)->save();
+    }
+    return array_keys($definition);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php
new file mode 100644
index 0000000..f79f8ae
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\StaticMenuLinkOverridesInterface.
+ */
+
+namespace Drupal\Core\Menu;
+
+/**
+ * Defines an interface for objects which overrides menu links defined in YAML.
+ */
+interface StaticMenuLinkOverridesInterface {
+
+  /**
+   * Loads any overrides to the definition of a static (YAML-defined) link.
+   *
+   * @param string $id
+   *   A menu link plugin ID.
+   *
+   * @return array|NULL
+   *   An override with following supported keys:
+   *     - parent
+   *     - weight
+   *     - menu_name
+   *     - expanded
+   *     - hidden
+   */
+  public function loadOverride($id);
+
+  /**
+   * Deletes any overrides to the definition of a static (YAML-defined) link.
+   *
+   * @param string $id
+   *   A menu link plugin ID.
+   */
+  public function deleteOverride($id);
+
+  /**
+   * Deletes multiple overrides to definitions of static (YAML-defined) links.
+   *
+   * @param array $ids
+   *   Array of menu link plugin IDs.
+   */
+  public function deleteMultipleOverrides(array $ids);
+
+  /**
+   * Loads overrides to multiple definitions of a static (YAML-defined) link.
+   *
+   * @param array $ids
+   *   Array of menu link plugin IDs.
+   *
+   * @return array
+   *   One or override keys by plugin ID.
+   *
+   * @see \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
+   */
+  public function loadMultipleOverrides(array $ids);
+
+  /**
+   * Saves the override.
+   *
+   * @param string $id
+   *   A menu link plugin ID.
+   * @param array $definition
+   *   The definition values to override. Supported keys:
+   *   - menu_name
+   *   - parent
+   *   - weight
+   *   - expanded
+   *   - hidden
+   *
+   * @return array
+   *   A list of properties which got saved.
+   */
+  public function saveOverride($id, array $definition);
+
+}
diff --git a/core/lib/Drupal/Core/ParamConverter/MenuLinkPluginConverter.php b/core/lib/Drupal/Core/ParamConverter/MenuLinkPluginConverter.php
new file mode 100644
index 0000000..b7c1479
--- /dev/null
+++ b/core/lib/Drupal/Core/ParamConverter/MenuLinkPluginConverter.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\ParamConverter\MenuLinkPluginConverter.
+ */
+
+namespace Drupal\Core\ParamConverter;
+
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Parameter converter for upcasting entity ids to full objects.
+ */
+class MenuLinkPluginConverter implements ParamConverterInterface {
+
+  /**
+   * Plugin manager which creates the instance from the value.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuTree;
+
+  /**
+   * Constructs a new MenuLinkPluginConverter.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   The menu link plugin manager.
+   */
+  public function __construct(MenuLinkTreeInterface $menu_tree) {
+    $this->menuTree = $menu_tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert($value, $definition, $name, array $defaults, Request $request) {
+    if ($value) {
+      try {
+        return $this->menuTree->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/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
index 882863d..cf2be0e 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
@@ -304,6 +304,7 @@ public function testBlockCacheTags() {
       'block_view:1',
       'block:powered',
       'block_plugin:system_powered_by_block',
+      'menu:account',
     );
     $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
     $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:stark');
@@ -345,6 +346,7 @@ public function testBlockCacheTags() {
       'block:powered-2',
       'block:powered',
       'block_plugin:system_powered_by_block',
+      'menu:account',
     );
     $this->assertEqual($cache_entry->tags, $expected_cache_tags);
     $expected_cache_tags = array(
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 9cc4835..fefe20e 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/comment/comment.module b/core/modules/comment/comment.module
index 9cc9a8b..82bdcd2 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -168,9 +168,9 @@ function comment_theme() {
 }
 
 /**
- * Implements hook_menu_link_defaults_alter()
+ * Implements hook_menu_links_alter()
  */
-function comment_menu_link_defaults_alter(&$links) {
+function comment_menu_links_alter(&$links) {
   if (isset($links['node.content_overview'])) {
     // Add comments to the description for admin/content if any.
     $links['node.content_overview']['description'] = 'Administer content and comments.';
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 8c76983..f010e18 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -201,9 +201,9 @@ function content_translation_entity_operation_alter(array &$operations, \Drupal\
 }
 
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_alter().
  */
-function content_translation_menu_link_defaults_alter(array &$links) {
+function content_translation_menu_links_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/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php
index a8e1a0c..0c8fd84 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php
@@ -48,7 +48,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 56ce643..646bec2 100644
--- a/core/modules/dblog/dblog.module
+++ b/core/modules/dblog/dblog.module
@@ -38,9 +38,9 @@ function dblog_help($route_name, Request $request) {
 }
 
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_alter().
  */
-function dblog_menu_link_defaults_alter(&$links) {
+function dblog_menu_links_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 9da1540..ad011f6 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -40,12 +40,12 @@ function editor_help($route_name, Request $request) {
 }
 
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_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_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/lib/Drupal/entity/Controller/EntityDisplayModeController.php b/core/modules/entity/lib/Drupal/entity/Controller/EntityDisplayModeController.php
index f0db942..9067f38 100644
--- a/core/modules/entity/lib/Drupal/entity/Controller/EntityDisplayModeController.php
+++ b/core/modules/entity/lib/Drupal/entity/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/lib/Drupal/help/Controller/HelpController.php b/core/modules/help/lib/Drupal/help/Controller/HelpController.php
index f9389bf..78b6863 100644
--- a/core/modules/help/lib/Drupal/help/Controller/HelpController.php
+++ b/core/modules/help/lib/Drupal/help/Controller/HelpController.php
@@ -106,8 +106,7 @@ public function helpPage($name, Request $request) {
       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/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
deleted file mode 100644
index d446643..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
+++ /dev/null
@@ -1,680 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\Entity\MenuLink.
- */
-
-namespace Drupal\menu_link\Entity;
-
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Component\Utility\UrlHelper;
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Entity\Entity;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Url;
-use Drupal\menu_link\MenuLinkInterface;
-use Symfony\Component\Routing\Route;
-
-/**
- * Defines the menu link entity class.
- *
- * @EntityType(
- *   id = "menu_link",
- *   label = @Translation("Menu link"),
- *   controllers = {
- *     "storage" = "Drupal\menu_link\MenuLinkStorage",
- *     "access" = "Drupal\menu_link\MenuLinkAccessController",
- *     "form" = {
- *       "default" = "Drupal\menu_link\MenuLinkForm"
- *     }
- *   },
- *   admin_permission = "administer menu",
- *   static_cache = FALSE,
- *   base_table = "menu_links",
- *   uri_callback = "menu_link_uri",
- *   translatable = TRUE,
- *   entity_keys = {
- *     "id" = "mlid",
- *     "label" = "link_title",
- *     "uuid" = "uuid",
- *     "bundle" = "bundle"
- *   },
- * )
- */
-class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
-
-  /**
-   * The link's menu name.
-   *
-   * @var string
-   */
-  public $menu_name = 'tools';
-
-  /**
-   * The link's bundle.
-   *
-   * @var string
-   */
-  public $bundle = 'tools';
-
-  /**
-   * The menu link ID.
-   *
-   * @var int
-   */
-  public $mlid;
-
-  /**
-   * An optional machine name if defined via the menu_link.static service.
-   *
-   * @var string
-   */
-  public $machine_name;
-
-  /**
-   * The menu link UUID.
-   *
-   * @var string
-   */
-  public $uuid;
-
-  /**
-   * The parent link ID.
-   *
-   * @var int
-   */
-  public $plid;
-
-  /**
-   * The Drupal path or external path this link points to.
-   *
-   * @var string
-   */
-  public $link_path;
-
-  /**
-   * The entity label.
-   *
-   * @var string
-   */
-  public $link_title = '';
-
-  /**
-   * A serialized array of options to be passed to the url() or l() function,
-   * such as a query string or HTML attributes.
-   *
-   * @var array
-   */
-  public $options = array();
-
-  /**
-   * The name of the module that generated this link.
-   *
-   * @var string
-   */
-  public $module = 'menu_ui';
-
-  /**
-   * A flag for whether the link should be rendered in menus.
-   *
-   * @var int
-   */
-  public $hidden = 0;
-
-  /**
-   * A flag to indicate if the link points to a full URL starting with a
-   * protocol, like http:// (1 = external, 0 = internal).
-   *
-   * @var int
-   */
-  public $external;
-
-  /**
-   * Flag indicating whether any links have this link as a parent.
-   *
-   * @var int
-   */
-  public $has_children = 0;
-
-  /**
-   * Flag for whether this link should be rendered as expanded in menus.
-   * Expanded links always have their child links displayed, instead of only
-   * when the link is in the active trail.
-   *
-   * @var int
-   */
-  public $expanded = 0;
-
-  /**
-   * Link weight among links in the same menu at the same depth.
-   *
-   * @var int
-   */
-  public $weight = 0;
-
-  /**
-   * The depth relative to the top level. A link with plid == 0 will have
-   * depth == 1.
-   *
-   * @var int
-   */
-  public $depth;
-
-  /**
-   * A flag to indicate that the user has manually created or edited the link.
-   *
-   * @var int
-   */
-  public $customized = 0;
-
-  /**
-   * The first entity ID in the materialized path.
-   *
-   * @var int
-   *
-   * @todo Investigate whether the p1, p2, .. pX properties can be moved to a
-   * single array property.
-   */
-  public $p1;
-
-  /**
-   * The second entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p2;
-
-  /**
-   * The third entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p3;
-
-  /**
-   * The fourth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p4;
-
-  /**
-   * The fifth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p5;
-
-  /**
-   * The sixth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p6;
-
-  /**
-   * The seventh entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p7;
-
-  /**
-   * The eighth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p8;
-
-  /**
-   * The ninth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p9;
-
-  /**
-   * The menu link modification timestamp.
-   *
-   * @var int
-   */
-  public $updated = 0;
-
-  /**
-   * The name of the route associated with this menu link, if any.
-   *
-   * @var string
-   */
-  public $route_name;
-
-  /**
-   * The parameters of the route associated with this menu link, if any.
-   *
-   * @var array
-   */
-  public $route_parameters = array();
-
-  /**
-   * The route object associated with this menu link, if any.
-   *
-   * @var \Symfony\Component\Routing\Route
-   */
-  protected $routeObject;
-
-  /**
-   * Boolean indicating whether a new revision should be created on save.
-   *
-   * @var bool
-   */
-  protected $newRevision = FALSE;
-
-  /**
-   * Indicates whether this is the default revision.
-   *
-   * @var bool
-   */
-  protected $isDefaultRevision = TRUE;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setNewRevision($value = TRUE) {
-    $this->newRevision = $value;
-  }
-  /**
-   * {@inheritdoc}
-   */
-  public function isNewRevision() {
-    return $this->newRevision || ($this->getEntityType()->hasKey('revision') && !$this->getRevisionId());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getRevisionId() {
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function isTranslatable() {
-    // @todo Inject the entity manager and retrieve bundle info from it.
-    $bundles = entity_get_bundles($this->entityTypeId);
-    return !empty($bundles[$this->bundle()]['translatable']);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
-  }
-
-  /**
-   * Overrides Entity::id().
-   */
-  public function id() {
-    return $this->mlid;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function bundle() {
-    return $this->bundle;
-  }
-
-  /**
-   * Overrides Entity::createDuplicate().
-   */
-  public function createDuplicate() {
-    $duplicate = parent::createDuplicate();
-    $duplicate->plid = NULL;
-    return $duplicate;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getRoute() {
-    if (!$this->route_name) {
-      return NULL;
-    }
-    if (!($this->routeObject instanceof Route)) {
-      $route_provider = \Drupal::service('router.route_provider');
-      $this->routeObject = $route_provider->getRouteByName($this->route_name);
-    }
-    return $this->routeObject;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setRouteObject(Route $route) {
-    $this->routeObject = $route;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function reset() {
-    // To reset the link to its original values, we need to retrieve its
-    // definition from the menu_link.static service. Otherwise, for example,
-    // the link's menu would not be reset, because properties like the original
-    // 'menu_name' are not stored anywhere else. Since resetting a link happens
-    // rarely and this is a one-time operation, retrieving the full set of
-    // default menu links does little harm.
-    $all_links = \Drupal::service('menu_link.static')->getLinks();
-    $original = $all_links[$this->machine_name];
-    $original['machine_name'] = $this->machine_name;
-    /** @var \Drupal\menu_link\MenuLinkStorageInterface $storage */
-    $storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
-    // @todo Do not create a new entity in order to update it, see
-    //   https://drupal.org/node/2241865
-    $new_link = $storage->createFromDefaultLink($original);
-    $new_link->setOriginalId($this->id());
-    // Allow the menu to be determined by the parent
-    if (!empty($new_link['parent']) && !empty($all_links[$new_link['parent']])) {
-      // Walk up the tree to find the menu name.
-      $parent = $all_links[$new_link['parent']];
-      $existing_parent = db_select('menu_links')
-        ->fields('menu_links')
-        ->condition('machine_name', $parent['machine_name'])
-        ->execute()->fetchAssoc();
-      if ($existing_parent) {
-        /** @var \Drupal\Core\Entity\EntityInterface $existing_parent */
-        $existing_parent = $storage->create($existing_parent);
-        $new_link->menu_name = $existing_parent->menu_name;
-        $new_link->plid = $existing_parent->id();
-      }
-    }
-    // Merge existing menu link's ID and 'has_children' property.
-    foreach (array('mlid', 'has_children') as $key) {
-      $new_link->{$key} = $this->{$key};
-    }
-    $new_link->save();
-    return $new_link;
-  }
-
-  /**
-   * Implements ArrayAccess::offsetExists().
-   */
-  public function offsetExists($offset) {
-    return isset($this->{$offset});
-  }
-
-  /**
-   * Implements ArrayAccess::offsetGet().
-   */
-  public function &offsetGet($offset) {
-    return $this->{$offset};
-  }
-
-  /**
-   * Implements ArrayAccess::offsetSet().
-   */
-  public function offsetSet($offset, $value) {
-    $this->{$offset} = $value;
-  }
-
-  /**
-   * Implements ArrayAccess::offsetUnset().
-   */
-  public function offsetUnset($offset) {
-    unset($this->{$offset});
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function preDelete(EntityStorageInterface $storage, array $entities) {
-    parent::preDelete($storage, $entities);
-
-    // Nothing to do if we don't want to reparent children.
-    if ($storage->getPreventReparenting()) {
-      return;
-    }
-
-    foreach ($entities as $entity) {
-      // Children get re-attached to the item's parent.
-      if ($entity->has_children) {
-        $children = $storage->loadByProperties(array('plid' => $entity->plid));
-        foreach ($children as $child) {
-          $child->plid = $entity->plid;
-          $storage->save($child);
-        }
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function postDelete(EntityStorageInterface $storage, array $entities) {
-    parent::postDelete($storage, $entities);
-
-    // Update the has_children status of the parent.
-    foreach ($entities as $entity) {
-      if (!$storage->getPreventReparenting()) {
-        $storage->updateParentalStatus($entity);
-      }
-    }
-
-    // Also clear the menu system static caches.
-    menu_reset_static_cache();
-    _menu_clear_page_cache();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preSave(EntityStorageInterface $storage) {
-    parent::preSave($storage);
-
-    // This is the easiest way to handle the unique internal path '<front>',
-    // since a path marked as external does not need to match a route.
-    $this->external = (UrlHelper::isExternal($this->link_path) || $this->link_path == '<front>') ? 1 : 0;
-
-    // Try to find a parent link. If found, assign it and derive its menu.
-    $parent = $this->findParent($storage);
-    if ($parent) {
-      $this->plid = $parent->id();
-      $this->menu_name = $parent->menu_name;
-    }
-    // If no corresponding parent link was found, move the link to the top-level.
-    else {
-      $this->plid = 0;
-    }
-
-    // Directly fill parents for top-level links.
-    if ($this->plid == 0) {
-      $this->p1 = $this->id();
-      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
-        $parent_property = "p$i";
-        $this->{$parent_property} = 0;
-      }
-      $this->depth = 1;
-    }
-    // Otherwise, ensure that this link's depth is not beyond the maximum depth
-    // and fill parents based on the parent link.
-    else {
-      if ($this->has_children && $this->original) {
-        $limit = MENU_MAX_DEPTH - $storage->findChildrenRelativeDepth($this->original) - 1;
-      }
-      else {
-        $limit = MENU_MAX_DEPTH - 1;
-      }
-      if ($parent->depth > $limit) {
-        return FALSE;
-      }
-      $this->depth = $parent->depth + 1;
-      $this->setParents($parent);
-    }
-
-    // Need to check both plid and menu_name, since plid can be 0 in any menu.
-    if (isset($this->original) && ($this->plid != $this->original->plid || $this->menu_name != $this->original->menu_name)) {
-      $storage->moveChildren($this);
-    }
-
-    // Find the route_name.
-    if (!$this->external && !isset($this->route_name)) {
-      $url = Url::createFromPath($this->link_path);
-      $this->route_name = $url->getRouteName();
-      $this->route_parameters = $url->getRouteParameters();
-    }
-    elseif (empty($this->link_path)) {
-      $this->link_path = \Drupal::urlGenerator()->getPathFromRoute($this->route_name, $this->route_parameters);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
-    parent::postSave($storage, $update);
-
-    // Check the has_children status of the parent.
-    $storage->updateParentalStatus($this);
-
-
-    // Entity::postSave() calls Entity::invalidateTagsOnSave(), which only
-    // handles the regular cases. The MenuLink entity has two special cases.
-    $cache_tags = array();
-    // Case 1: a newly created menu link is *also* added to a menu, so we must
-    // invalidate the associated menu's cache tag.
-    if (!$update) {
-      $cache_tags = $this->getCacheTag();
-    }
-    // Case 2: a menu link may be moved from one menu to another; the original
-    // menu's cache tag must also be invalidated.
-    if (isset($this->original) && $this->menu_name != $this->original->menu_name) {
-      $cache_tags = NestedArray::mergeDeep($cache_tags, $this->original->getCacheTag());
-    }
-    Cache::invalidateTags($cache_tags);
-
-    // Also clear the menu system static caches.
-    menu_reset_static_cache();
-
-    // Now clear the cache.
-    _menu_clear_page_cache();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function postLoad(EntityStorageInterface $storage, array &$entities) {
-    parent::postLoad($storage, $entities);
-
-    $routes = array();
-    foreach ($entities as $menu_link) {
-      $menu_link->options = unserialize($menu_link->options);
-      $menu_link->route_parameters = unserialize($menu_link->route_parameters);
-
-      // By default use the menu_name as type.
-      $menu_link->bundle = $menu_link->menu_name;
-
-      // For all links that have an associated route, load the route object now
-      // and save it on the object. That way we avoid a select N+1 problem later.
-      if ($menu_link->route_name) {
-        $routes[$menu_link->id()] = $menu_link->route_name;
-      }
-    }
-
-    // Now mass-load any routes needed and associate them.
-    if ($routes) {
-      $route_objects = \Drupal::service('router.route_provider')->getRoutesByNames($routes);
-      foreach ($routes as $entity_id => $route) {
-        // Not all stored routes will be valid on load.
-        if (isset($route_objects[$route])) {
-          $entities[$entity_id]->setRouteObject($route_objects[$route]);
-        }
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setParents(MenuLinkInterface $parent) {
-    $i = 1;
-    while ($i < $this->depth) {
-      $p = 'p' . $i++;
-      $this->{$p} = $parent->{$p};
-    }
-    $p = 'p' . $i++;
-    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
-    $this->{$p} = $this->id();
-    while ($i <= MENU_MAX_DEPTH) {
-      $p = 'p' . $i++;
-      $this->{$p} = 0;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function findParent(EntityStorageInterface $storage) {
-    $parent = FALSE;
-
-    // This item is explicitly top-level, skip the rest of the parenting.
-    if (isset($this->plid) && empty($this->plid)) {
-      return $parent;
-    }
-
-    // If we have a parent link ID, try to use that.
-    $candidates = array();
-    if (isset($this->plid)) {
-      $candidates[] = $this->plid;
-    }
-
-    // Else, if we have a link hierarchy try to find a valid parent in there.
-    if (!empty($this->depth) && $this->depth > 1) {
-      for ($depth = $this->depth - 1; $depth >= 1; $depth--) {
-        $parent_property = "p$depth";
-        $candidates[] = $this->$parent_property;
-      }
-    }
-
-    foreach ($candidates as $mlid) {
-      $parent = $storage->load($mlid);
-      if ($parent) {
-        break;
-      }
-    }
-    return $parent;
-  }
-
-  /**
-   * Builds and returns the renderable array for this menu link.
-   *
-   * @return array
-   *   A renderable array representing the content of the link.
-   */
-  public function build() {
-    $build = array(
-      '#type' => 'link',
-      '#title' => $this->title,
-      '#href' => $this->href,
-      '#route_name' => $this->route_name ? $this->route_name : NULL,
-      '#route_parameters' => $this->route_parameters,
-      '#options' => !empty($this->localized_options) ? $this->localized_options : array(),
-    );
-    return $build;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheTag() {
-    return entity_load('menu', $this->menu_name)->getCacheTag();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getListCacheTags() {
-    return entity_load('menu', $this->menu_name)->getListCacheTags();
-  }
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php
deleted file mode 100644
index c6e6b91..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkAccessController.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityAccessController;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Session\AccountInterface;
-
-/**
- * Defines an access controller for the menu link entity.
- *
- * @see \Drupal\menu_link\Entity\MenuLink
- */
-class MenuLinkAccessController extends EntityAccessController {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
-    $access = $account->hasPermission('administer menu');
-    if ($access) {
-      switch ($operation) {
-        case 'reset':
-          // Reset allowed for items defined via hook_menu() and customized.
-          return !empty($entity->machine_name) && $entity->customized;
-
-        case 'delete':
-          // Only items created by the Menu UI module can be deleted.
-          return $entity->module == 'menu_ui' || $entity->updated == 1;
-
-      }
-    }
-    return $access;
-  }
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkForm.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkForm.php
deleted file mode 100644
index c365dae..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkForm.php
+++ /dev/null
@@ -1,311 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkForm.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Component\Utility\UrlHelper;
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Language\Language;
-use Drupal\Core\Path\AliasManagerInterface;
-use Drupal\Core\Routing\UrlGenerator;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Form controller for the node edit forms.
- */
-class MenuLinkForm extends EntityForm {
-
-  /**
-   * The menu link storage.
-   *
-   * @var \Drupal\menu_link\MenuLinkStorageInterface
-   */
-  protected $menuLinkStorage;
-
-  /**
-   * The path alias manager.
-   *
-   * @var \Drupal\Core\Path\AliasManagerInterface
-   */
-  protected $pathAliasManager;
-
-  /**
-   * The URL generator.
-   *
-   * @var \Drupal\Core\Routing\UrlGenerator
-   */
-  protected $urlGenerator;
-
-  /**
-   * Constructs a new MenuLinkForm object.
-   *
-   * @param \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage
-   *   The menu link storage.
-   * @param \Drupal\Core\Path\AliasManagerInterface $path_alias_manager
-   *   The path alias manager.
-   * @param \Drupal\Core\Routing\UrlGenerator $url_generator
-   *   The URL generator.
-   */
-  public function __construct(MenuLinkStorageInterface $menu_link_storage, AliasManagerInterface $path_alias_manager, UrlGenerator $url_generator) {
-    $this->menuLinkStorage = $menu_link_storage;
-    $this->pathAliasManager = $path_alias_manager;
-    $this->urlGenerator = $url_generator;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('entity.manager')->getStorage('menu_link'),
-      $container->get('path.alias_manager.cached'),
-      $container->get('url_generator')
-    );
-  }
-
-  /**
-   * Overrides EntityForm::form().
-   */
-  public function form(array $form, array &$form_state) {
-    $menu_link = $this->entity;
-    // Since menu_link_load() no longer returns a translated and access checked
-    // item, do it here instead.
-    _menu_link_translate($menu_link);
-
-    $form['link_title'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Menu link title'),
-      '#default_value' => $menu_link->link_title,
-      '#description' => t('The text to be used for this link in the menu.'),
-      '#required' => TRUE,
-    );
-    foreach (array('link_path', 'mlid', 'module', 'has_children', 'options') as $key) {
-      $form[$key] = array('#type' => 'value', '#value' => $menu_link->{$key});
-    }
-    // Any item created or edited via this interface is considered "customized".
-    $form['customized'] = array('#type' => 'value', '#value' => 1);
-
-    // We are not using url() when constructing this path because it would add
-    // $base_path.
-    $path = $menu_link->link_path;
-    if (isset($menu_link->options['query'])) {
-      $path .= '?' . $this->urlGenerator->httpBuildQuery($menu_link->options['query']);
-    }
-    if (isset($menu_link->options['fragment'])) {
-      $path .= '#' . $menu_link->options['fragment'];
-    }
-    if ($menu_link->module == 'menu_ui') {
-      $form['link_path'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Path'),
-        '#maxlength' => 255,
-        '#default_value' => $path,
-        '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
-        '#required' => TRUE,
-      );
-    }
-    else {
-      $form['_path'] = array(
-        '#type' => 'item',
-        '#title' => t('Path'),
-        '#description' => l($menu_link->link_title, $menu_link->href, $menu_link->options),
-      );
-    }
-
-    $form['description'] = array(
-      '#type' => 'textarea',
-      '#title' => t('Description'),
-      '#default_value' => isset($menu_link->options['attributes']['title']) ? $menu_link->options['attributes']['title'] : '',
-      '#rows' => 1,
-      '#description' => t('Shown when hovering over the menu link.'),
-    );
-    $form['enabled'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Enabled'),
-      '#default_value' => !$menu_link->hidden,
-      '#description' => t('Menu links that are not enabled will not be listed in any menu.'),
-    );
-    $form['expanded'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Show as expanded'),
-      '#default_value' => $menu_link->expanded,
-      '#description' => t('If selected and this menu link has children, the menu will always appear expanded.'),
-    );
-
-    // Generate a list of possible parents (not including this link or descendants).
-    $options = menu_ui_parent_options(menu_ui_get_menus(), $menu_link);
-    $default = $menu_link->menu_name . ':' . $menu_link->plid;
-    if (!isset($options[$default])) {
-      $default = 'tools:0';
-    }
-    $form['parent'] = array(
-      '#type' => 'select',
-      '#title' => t('Parent link'),
-      '#default_value' => $default,
-      '#options' => $options,
-      '#description' => t('The maximum depth for a link and all its children is fixed at !maxdepth. Some menu links may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
-      '#attributes' => array('class' => array('menu-title-select')),
-    );
-
-    // Get number of items in menu so the weight selector is sized appropriately.
-    $delta = $this->menuLinkStorage->countMenuLinks($menu_link->menu_name);
-    $form['weight'] = array(
-      '#type' => 'weight',
-      '#title' => t('Weight'),
-      // Old hardcoded value.
-      '#delta' => max($delta, 50),
-      '#default_value' => $menu_link->weight,
-      '#description' => t('Optional. In the menu, the heavier links will sink and the lighter links will be positioned nearer the top.'),
-    );
-
-    // Language module allows to configure the menu link language independently
-    // of the menu language. It also allows to optionally show the language
-    // selector on the menu link form so that the language of each menu link can
-    // be configured individually.
-    if ($this->moduleHandler->moduleExists('language')) {
-      $language_configuration = language_get_default_configuration('menu_link', $menu_link->bundle());
-      $default_langcode = ($menu_link->isNew() ? $language_configuration['langcode'] : $menu_link->langcode);
-      $language_show = $language_configuration['language_show'];
-    }
-    // Without Language module menu links inherit the menu language and no
-    // language selector is shown.
-    else {
-      $default_langcode = ($menu_link->isNew() ? entity_load('menu', $menu_link->menu_name)->langcode : $menu_link->langcode);
-      $language_show = FALSE;
-    }
-
-    $form['langcode'] = array(
-      '#type' => 'language_select',
-      '#title' => t('Language'),
-      '#languages' => Language::STATE_ALL,
-      '#default_value' => $default_langcode,
-      '#access' => $language_show,
-    );
-
-    return parent::form($form, $form_state, $menu_link);
-  }
-
-  /**
-   * Overrides EntityForm::actions().
-   */
-  protected function actions(array $form, array &$form_state) {
-    $element = parent::actions($form, $form_state);
-    $element['submit']['#button_type'] = 'primary';
-    return $element;
-  }
-
-  /**
-   * Overrides EntityForm::validate().
-   */
-  public function validate(array $form, array &$form_state) {
-    $menu_link = $this->buildEntity($form, $form_state);
-
-    $normal_path = $this->pathAliasManager->getPathByAlias($menu_link->link_path);
-    if ($menu_link->link_path != $normal_path) {
-      drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $menu_link->link_path, '%normal_path' => $normal_path)));
-      $menu_link->link_path = $normal_path;
-      $form_state['values']['link_path'] = $normal_path;
-    }
-    if (!UrlHelper::isExternal($menu_link->link_path)) {
-      $parsed_link = parse_url($menu_link->link_path);
-      if (isset($parsed_link['query'])) {
-        $menu_link->options['query'] = array();
-        parse_str($parsed_link['query'], $menu_link->options['query']);
-      }
-      else {
-        // Use unset() rather than setting to empty string
-        // to avoid redundant serialized data being stored.
-        unset($menu_link->options['query']);
-      }
-      if (isset($parsed_link['fragment'])) {
-        $menu_link->options['fragment'] = $parsed_link['fragment'];
-      }
-      else {
-        unset($menu_link->options['fragment']);
-      }
-      if (isset($parsed_link['path']) && $menu_link->link_path != $parsed_link['path']) {
-        $menu_link->link_path = $parsed_link['path'];
-      }
-    }
-    if (!trim($menu_link->link_path) || !drupal_valid_path($menu_link->link_path, TRUE)) {
-      $this->setFormError('link_path', $form_state, $this->t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->link_path)));
-    }
-
-    parent::validate($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildEntity(array $form, array &$form_state) {
-    // @todo: Remove this when menu links are converted to content entities in
-    //   http://drupal.org/node/1842858.
-    $entity = clone $this->entity;
-    // If you submit a form, the form state comes from caching, which forces
-    // the controller to be the one before caching. Ensure to have the
-    // controller of the current request.
-    $form_state['controller'] = $this;
-
-    // Copy top-level form values to entity properties, without changing
-    // existing entity properties that are not being edited by
-    // this form.
-    foreach ($form_state['values'] as $key => $value) {
-      $entity->$key = $value;
-    }
-
-    // Invoke all specified builders for copying form values to entity properties.
-    if (isset($form['#entity_builders'])) {
-      foreach ($form['#entity_builders'] as $function) {
-        call_user_func_array($function, array($entity->getEntityTypeId(), $entity, &$form, &$form_state));
-      }
-    }
-
-    return $entity;
-  }
-
-  /**
-   * Overrides EntityForm::submit().
-   */
-  public function submit(array $form, array &$form_state) {
-    // Build the menu link object from the submitted values.
-    $menu_link = parent::submit($form, $form_state);
-
-    // The value of "hidden" is the opposite of the value supplied by the
-    // "enabled" checkbox.
-    $menu_link->hidden = (int) !$menu_link->enabled;
-    unset($menu_link->enabled);
-
-    $menu_link->options['attributes']['title'] = $menu_link->description;
-    list($menu_link->menu_name, $menu_link->plid) = explode(':', $menu_link->parent);
-
-    return $menu_link;
-  }
-
-  /**
-   * Overrides EntityForm::save().
-   */
-  public function save(array $form, array &$form_state) {
-    $menu_link = $this->entity;
-
-    $saved = $menu_link->save();
-
-    if ($saved) {
-      drupal_set_message(t('The menu link has been saved.'));
-      $form_state['redirect_route'] = array(
-        'route_name' => 'menu_ui.menu_edit',
-        'route_parameters' => array(
-          'menu' => $menu_link->menu_name,
-        ),
-      );
-    }
-    else {
-      drupal_set_message(t('There was an error saving the menu link.'), 'error');
-      $form_state['rebuild'] = TRUE;
-    }
-  }
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php
deleted file mode 100644
index fdfab67..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkInterface.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityInterface;
-use Symfony\Component\Routing\Route;
-
-/**
- * Provides an interface defining a menu link entity.
- */
-interface MenuLinkInterface extends EntityInterface {
-
-  /**
-   * Returns the Route object associated with this link, if any.
-   *
-   * @return \Symfony\Component\Routing\Route|null
-   *   The route object for this menu link, or NULL if there isn't one.
-   */
-  public function getRoute();
-
-  /**
-   * Sets the route object for this link.
-   *
-   * This should only be called by MenuLinkStorage when loading
-   * the link object. Calling it at other times could result in unpredictable
-   * behavior.
-   *
-   * @param \Symfony\Component\Routing\Route $route
-   */
-  public function setRouteObject(Route $route);
-
-  /**
-   * Resets a system-defined menu link.
-   *
-   * @return \Drupal\menu_link\MenuLinkInterface
-   *   A menu link entity.
-   */
-  public function reset();
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php
deleted file mode 100644
index 60871d0..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkStorage.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityDatabaseStorage;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageException;
-
-/**
- * Controller class for menu links.
- *
- * This extends the Drupal\entity\EntityDatabaseStorage class, adding
- * required special handling for menu_link entities.
- */
-class MenuLinkStorage extends EntityDatabaseStorage implements MenuLinkStorageInterface {
-
-  /**
-   * Indicates whether the delete operation should re-parent children items.
-   *
-   * @var bool
-   */
-  protected $preventReparenting = FALSE;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function create(array $values = array()) {
-    // The bundle of menu links being the menu name is not enforced but is the
-    // default behavior if no bundle is set.
-    if (!isset($values['bundle']) && isset($values['menu_name'])) {
-      $values['bundle'] = $values['menu_name'];
-    }
-    return parent::create($values);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(EntityInterface $entity) {
-
-    // We return SAVED_UPDATED by default because the logic below might not
-    // update the entity if its values haven't changed, so returning FALSE
-    // would be confusing in that situation.
-    $return = SAVED_UPDATED;
-
-    $transaction = $this->database->startTransaction();
-    try {
-      // Load the stored entity, if any.
-      if (!$entity->isNew() && !isset($entity->original)) {
-        $id = $entity->id();
-        if ($entity->getOriginalId() !== NULL) {
-          $id = $entity->getOriginalId();
-        }
-        $entity->original = $this->loadUnchanged($id);
-      }
-
-      if ($entity->isNew()) {
-        $entity->mlid = $this->database->insert($this->entityType->getBaseTable())->fields(array('menu_name' => $entity->menu_name))->execute();
-        $entity->enforceIsNew();
-      }
-
-      // Unlike the save() method from EntityDatabaseStorage, we invoke the
-      // 'presave' hook first because we want to allow modules to alter the
-      // entity before all the logic from our preSave() method.
-      $this->invokeHook('presave', $entity);
-      $entity->preSave($this);
-
-      // If every value in $entity->original is the same in the $entity, there
-      // is no reason to run the update queries or clear the caches. We use
-      // array_intersect_key() with the $entity as the first parameter because
-      // $entity may have additional keys left over from building a router entry.
-      // The intersect removes the extra keys, allowing a meaningful comparison.
-      if ($entity->isNew() || (array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original))) {
-        $return = drupal_write_record($this->entityType->getBaseTable(), $entity, $this->idKey);
-
-        if ($return) {
-          if (!$entity->isNew()) {
-            $this->resetCache(array($entity->{$this->idKey}));
-            $entity->postSave($this, TRUE);
-            $this->invokeHook('update', $entity);
-          }
-          else {
-            $return = SAVED_NEW;
-            $this->resetCache();
-
-            $entity->enforceIsNew(FALSE);
-            $entity->postSave($this, FALSE);
-            $this->invokeHook('insert', $entity);
-          }
-        }
-      }
-
-      // Ignore slave server temporarily.
-      db_ignore_slave();
-      unset($entity->original);
-
-      return $return;
-    }
-    catch (\Exception $e) {
-      $transaction->rollback();
-      watchdog_exception($this->entityTypeId, $e);
-      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setPreventReparenting($value = FALSE) {
-    $this->preventReparenting = $value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getPreventReparenting() {
-    return $this->preventReparenting;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function loadUpdatedCustomized(array $router_paths) {
-    $query = parent::buildQuery(NULL);
-    $query
-      ->condition(db_or()
-      ->condition('updated', 1)
-      ->condition(db_and()
-        ->condition('router_path', $router_paths, 'NOT IN')
-        ->condition('external', 0)
-        ->condition('customized', 1)
-        )
-      );
-    $query_result = $query->execute();
-
-    // We provide the necessary arguments for PDO to create objects of the
-    // specified entity class.
-    // @see \Drupal\Core\Entity\EntityInterface::__construct()
-    $query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityClass, array(array(), $this->entityTypeId));
-
-    return $query_result->fetchAllAssoc($this->idKey);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function loadModuleAdminTasks() {
-    // @todo - this code will move out of the menu link entity, so we are doing
-    //   a straight SQL query for expediency.
-    $result = $this->database->select('menu_links');
-    $result->condition('machine_name', 'system.admin');
-    $result->addField('menu_links', 'mlid');
-    $plid = $result->execute()->fetchField();
-
-    $query = $this->database->select('menu_links', 'base', array('fetch' => \PDO::FETCH_ASSOC));
-    $query->fields('base');
-    $query
-      ->condition('base.hidden', 0, '>=')
-      ->condition('base.module', '', '>')
-      ->condition('base.machine_name', '', '>')
-      ->condition('base.p1', $plid);
-    $entities = $query->execute()->fetchAll();
-
-    return $entities;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) {
-    // If plid == 0, there is nothing to update.
-    if ($entity->plid) {
-      // Check if at least one visible child exists in the table.
-      $query = $this->getQuery();
-      $query
-        ->condition('menu_name', $entity->menu_name)
-        ->condition('hidden', 0)
-        ->condition('plid', $entity->plid)
-        ->count();
-
-      if ($exclude) {
-        $query->condition('mlid', $entity->id(), '<>');
-      }
-
-      $parent_has_children = ((bool) $query->execute()) ? 1 : 0;
-      $this->database->update('menu_links')
-        ->fields(array('has_children' => $parent_has_children))
-        ->condition('mlid', $entity->plid)
-        ->execute();
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function findChildrenRelativeDepth(EntityInterface $entity) {
-    // @todo Since all we need is a specific field from the base table, does it
-    // make sense to convert to EFQ?
-    $query = $this->database->select('menu_links');
-    $query->addField('menu_links', 'depth');
-    $query->condition('menu_name', $entity->menu_name);
-    $query->orderBy('depth', 'DESC');
-    $query->range(0, 1);
-
-    $i = 1;
-    $p = 'p1';
-    while ($i <= MENU_MAX_DEPTH && $entity->{$p}) {
-      $query->condition($p, $entity->{$p});
-      $p = 'p' . ++$i;
-    }
-
-    $max_depth = $query->execute()->fetchField();
-
-    return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function moveChildren(EntityInterface $entity) {
-    $query = $this->database->update($this->entityType->getBaseTable());
-
-    $query->fields(array('menu_name' => $entity->menu_name));
-
-    $p = 'p1';
-    $expressions = array();
-    for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) {
-      $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}));
-    }
-    $j = $entity->original->depth + 1;
-    while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
-      $expressions[] = array('p' . $i++, 'p' . $j++, array());
-    }
-    while ($i <= MENU_MAX_DEPTH) {
-      $expressions[] = array('p' . $i++, 0, array());
-    }
-
-    $shift = $entity->depth - $entity->original->depth;
-    if ($shift > 0) {
-      // The order of expressions must be reversed so the new values don't
-      // overwrite the old ones before they can be used because "Single-table
-      // UPDATE assignments are generally evaluated from left to right"
-      // @see http://dev.mysql.com/doc/refman/5.0/en/update.html
-      $expressions = array_reverse($expressions);
-    }
-    foreach ($expressions as $expression) {
-      $query->expression($expression[0], $expression[1], $expression[2]);
-    }
-
-    $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
-    $query->condition('menu_name', $entity->original->menu_name);
-    $p = 'p1';
-    for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) {
-      $query->condition($p, $entity->original->{$p});
-    }
-
-    $query->execute();
-
-    // Check the has_children status of the parent, while excluding this item.
-    $this->updateParentalStatus($entity->original, TRUE);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function countMenuLinks($menu_name) {
-    $query = $this->getQuery();
-    $query
-      ->condition('menu_name', $menu_name)
-      ->count();
-    return $query->execute();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getParentFromHierarchy(EntityInterface $entity) {
-    $parent_path = $entity->link_path;
-    do {
-      $parent = FALSE;
-      $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
-
-      $query = $this->getQuery();
-      $query
-        ->condition('mlid', $entity->id(), '<>')
-        ->condition('module', 'system')
-        // We always respect the link's 'menu_name'; inheritance for router
-        // items is ensured in _menu_router_build().
-        ->condition('menu_name', $entity->menu_name)
-        ->condition('link_path', $parent_path);
-
-      $result = $query->execute();
-      // Only valid if we get a unique result.
-      if (count($result) == 1) {
-        $parent = $this->load(reset($result));
-      }
-    } while ($parent === FALSE && $parent_path);
-
-    return $parent;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function createFromDefaultLink(array $item) {
-    // Suggested items are disabled by default.
-    $item += array(
-      'hidden' => 0,
-      'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
-    );
-    return $this->create($item);
-  }
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php
deleted file mode 100644
index 3d3fd5d..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkStorageInterface.
-*/
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-
-/**
- * Defines a common interface for menu link entity controller classes.
- */
-interface MenuLinkStorageInterface extends EntityStorageInterface {
-
-  /**
-   * Sets an internal flag that allows us to prevent the reparenting operations
-   * executed during deletion.
-   *
-   * @param bool $value
-   *   TRUE if reparenting should be allowed, FALSE if it should be prevented.
-   */
-  public function setPreventReparenting($value = FALSE);
-
-  /**
-   * Gets value of internal flag that allows/prevents reparenting operations
-   * executed during deletion.
-   *
-   * @return bool
-   *   TRUE if reparenting is allowed, FALSE if it is prevented.
-   */
-  public function getPreventReparenting();
-
-  /**
-   * Loads system menu link as needed by system_get_module_admin_tasks().
-   *
-   * @return array
-   *   An array of menu link entities indexed by their IDs.
-   */
-  public function loadModuleAdminTasks();
-
-  /**
-   * Checks and updates the 'has_children' property for the parent of a link.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   */
-  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE);
-
-  /**
-   * Finds the depth of an item's children relative to its depth.
-   *
-   * For example, if the item has a depth of 2 and the maximum of any child in
-   * the menu link tree is 5, the relative depth is 3.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   *
-   * @return int
-   *   The relative depth, or zero.
-   */
-  public function findChildrenRelativeDepth(EntityInterface $entity);
-
-  /**
-   * Updates the children of a menu link that is being moved.
-   *
-   * The menu name, parents (p1 - p6), and depth are updated for all children of
-   * the link, and the has_children status of the previous parent is updated.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   */
-  public function moveChildren(EntityInterface $entity);
-
-  /**
-   * Returns the number of menu links from a menu.
-   *
-   * @param string $menu_name
-   *   The unique name of a menu.
-   */
-  public function countMenuLinks($menu_name);
-
-  /**
-   * Tries to derive menu link's parent from the path hierarchy.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface|false
-   *   A menu link entity or FALSE if not valid parent was found.
-   */
-  public function getParentFromHierarchy(EntityInterface $entity);
-
-  /**
-   * Builds a menu link entity from a default item.
-   *
-   * This function should only be called for link data from
-   * the menu_link.static service.
-   *
-   * @param array $item
-   *   An item returned from the menu_link.static service.
-   *
-   * @return \Drupal\menu_link\MenuLinkInterface
-   *   A menu link entity.
-   */
-  public function createFromDefaultLink(array $item);
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php
deleted file mode 100644
index adbf85d..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php
+++ /dev/null
@@ -1,619 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuTree.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\Query\QueryFactory;
-use Drupal\Core\State\StateInterface;
-use Drupal\Core\Language\LanguageManagerInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\RequestStack;
-
-/**
- * Provides the default implementation of a menu tree.
- */
-class MenuTree implements MenuTreeInterface {
-
-  /**
-   * The database connection.
-   *
-   * @var \Drupal\Core\Database\Connection
-   *   The database connection.
-   */
-  protected $database;
-
-  /**
-   * The cache backend.
-   *
-   * @var \Drupal\Core\Cache\CacheBackendInterface
-   */
-  protected $cache;
-
-  /**
-   * The language manager.
-   *
-   * @var \Drupal\Core\Language\LanguageManagerInterface
-   */
-  protected $languageManager;
-
-  /**
-   * The request stack.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
-   */
-  protected $requestStack;
-
-  /**
-   * The menu link storage.
-   *
-   * @var \Drupal\menu_link\MenuLinkStorageInterface
-   */
-  protected $menuLinkStorage;
-
-  /**
-   * The entity query factory.
-   *
-   * @var \Drupal\Core\Entity\Query\QueryFactory
-   */
-  protected $queryFactory;
-
-  /**
-   * The state.
-   *
-   * @var \Drupal\Core\State\StateInterface
-   */
-  protected $state;
-
-  /**
-   * A list of active trail paths keyed by $menu_name.
-   *
-   * @var array
-   */
-  protected $trailPaths;
-
-  /**
-   * Stores the rendered menu output keyed by $menu_name.
-   *
-   * @var array
-   */
-  protected $menuOutput;
-
-  /**
-   * Stores the menu tree used by the doBuildTree method, keyed by a cache ID.
-   *
-   * This cache ID is built using the $menu_name, the current language and
-   * some parameters passed into an entity query.
-   */
-  protected $menuTree;
-
-  /**
-   * Stores the full menu tree data keyed by a cache ID.
-   *
-   * This variable distinct from static::$menuTree by having also items without
-   * access by the current user.
-   *
-   * This cache ID is built with the menu name, a passed in root link ID, the
-   * current language as well as the maximum depth.
-   *
-   * @var array
-   */
-  protected $menuFullTrees;
-
-  /**
-   * Stores the menu tree data on the current page keyed by a cache ID.
-   *
-   * This contains less information than a tree built with buildAllData.
-   *
-   * @var array
-   */
-  protected $menuPageTrees;
-
-  /**
-   * Constructs a new MenuTree.
-   *
-   * @param \Drupal\Core\Database\Connection $database
-   *   The database connection.
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
-   *   The cache backend.
-   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
-   *   The language manager.
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
-   *   The request stack.
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
-   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
-   *   The entity query factory.
-   * @param \Drupal\Core\State\StateInterface $state
-   *   The state.
-   */
-  public function __construct(Connection $database, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, QueryFactory $entity_query_factory, StateInterface $state) {
-    $this->database = $database;
-    $this->cache = $cache_backend;
-    $this->languageManager = $language_manager;
-    $this->requestStack = $request_stack;
-    $this->menuLinkStorage = $entity_manager->getStorage('menu_link');
-    $this->queryFactory = $entity_query_factory;
-    $this->state = $state;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildAllData($menu_name, $link = NULL, $max_depth = NULL) {
-    $language_interface = $this->languageManager->getCurrentLanguage();
-
-    // Use $mlid as a flag for whether the data being loaded is for the whole
-    // tree.
-    $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
-    // Generate a cache ID (cid) specific for this $menu_name, $link, $language,
-    // and depth.
-    $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->id . ':' . (int) $max_depth;
-
-    if (!isset($this->menuFullTrees[$cid])) {
-      // If the static variable doesn't have the data, check {cache_menu}.
-      $cache = $this->cache->get($cid);
-      if ($cache && $cache->data) {
-        // If the cache entry exists, it contains the parameters for
-        // menu_build_tree().
-        $tree_parameters = $cache->data;
-      }
-      // If the tree data was not in the cache, build $tree_parameters.
-      if (!isset($tree_parameters)) {
-        $tree_parameters = array(
-          'min_depth' => 1,
-          'max_depth' => $max_depth,
-        );
-        if ($mlid) {
-          // The tree is for a single item, so we need to match the values in
-          // its p columns and 0 (the top level) with the plid values of other
-          // links.
-          $parents = array(0);
-          for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
-            if (!empty($link["p$i"])) {
-              $parents[] = $link["p$i"];
-            }
-          }
-          $tree_parameters['expanded'] = $parents;
-          $tree_parameters['active_trail'] = $parents;
-          $tree_parameters['active_trail'][] = $mlid;
-        }
-
-        // Cache the tree building parameters using the page-specific cid.
-        $this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
-      }
-
-      // Build the tree using the parameters; the resulting tree will be cached
-      // by $this->doBuildTree()).
-      $this->menuFullTrees[$cid] = $this->buildTree($menu_name, $tree_parameters);
-    }
-
-    return $this->menuFullTrees[$cid];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
-    $language_interface = $this->languageManager->getCurrentLanguage();
-
-    // Load the request corresponding to the current page.
-    $request = $this->requestStack->getCurrentRequest();
-    $system_path = NULL;
-    if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
-      // @todo https://drupal.org/node/2068471 is adding support so we can tell
-      // if this is called on a 404/403 page.
-      $system_path = $request->attributes->get('_system_path');
-      $page_not_403 = 1;
-    }
-    if (isset($system_path)) {
-      if (isset($max_depth)) {
-        $max_depth = min($max_depth, MENU_MAX_DEPTH);
-      }
-      // Generate a cache ID (cid) specific for this page.
-      $cid = 'links:' . $menu_name . ':page:' . $system_path . ':' . $language_interface->id . ':' . $page_not_403 . ':' . (int) $max_depth;
-      // If we are asked for the active trail only, and $menu_name has not been
-      // built and cached for this page yet, then this likely means that it
-      // won't be built anymore, as this function is invoked from
-      // template_preprocess_page(). So in order to not build a giant menu tree
-      // that needs to be checked for access on all levels, we simply check
-      // whether we have the menu already in cache, or otherwise, build a
-      // minimum tree containing the active trail only.
-      if (!isset($this->menuPageTrees[$cid]) && $only_active_trail) {
-        $cid .= ':trail';
-      }
-
-      if (!isset($this->menuPageTrees[$cid])) {
-        // If the static variable doesn't have the data, check {cache_menu}.
-        $cache = $this->cache->get($cid);
-        if ($cache && $cache->data) {
-          // If the cache entry exists, it contains the parameters for
-          // menu_build_tree().
-          $tree_parameters = $cache->data;
-        }
-        // If the tree data was not in the cache, build $tree_parameters.
-        if (!isset($tree_parameters)) {
-          $tree_parameters = array(
-            'min_depth' => 1,
-            'max_depth' => $max_depth,
-          );
-          $active_trail = $this->getActiveTrailIds($menu_name);
-
-          // If this page is accessible to the current user, build the tree
-          // parameters accordingly.
-          if ($page_not_403) {
-            // The active trail contains more than only array(0 => 0).
-            if (count($active_trail) > 1) {
-              // If we are asked to build links for the active trail only,skip
-              // the entire 'expanded' handling.
-              if ($only_active_trail) {
-                $tree_parameters['only_active_trail'] = TRUE;
-              }
-            }
-            $parents = $active_trail;
-
-            $expanded = $this->state->get('menu_expanded');
-            // Check whether the current menu has any links set to be expanded.
-            if (!$only_active_trail && $expanded && in_array($menu_name, $expanded)) {
-              // Collect all the links set to be expanded, and then add all of
-              // their children to the list as well.
-              do {
-                $query = $this->queryFactory->get('menu_link')
-                  ->condition('menu_name', $menu_name)
-                  ->condition('expanded', 1)
-                  ->condition('has_children', 1)
-                  ->condition('plid', $parents, 'IN')
-                  ->condition('mlid', $parents, 'NOT IN');
-                $result = $query->execute();
-                $parents += $result;
-              } while (!empty($result));
-            }
-            $tree_parameters['expanded'] = $parents;
-            $tree_parameters['active_trail'] = $active_trail;
-          }
-          // If access is denied, we only show top-level links in menus.
-          else {
-            $tree_parameters['expanded'] = $active_trail;
-            $tree_parameters['active_trail'] = $active_trail;
-          }
-          // Cache the tree building parameters using the page-specific cid.
-          $this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
-        }
-
-        // Build the tree using the parameters; the resulting tree will be
-        // cached by $tihs->buildTree().
-        $this->menuPageTrees[$cid] = $this->buildTree($menu_name, $tree_parameters);
-      }
-      return $this->menuPageTrees[$cid];
-    }
-
-    return array();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getActiveTrailIds($menu_name) {
-    // Parent mlids; used both as key and value to ensure uniqueness.
-    // We always want all the top-level links with plid == 0.
-    $active_trail = array(0 => 0);
-
-    $request = $this->requestStack->getCurrentRequest();
-
-    if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
-      // @todo https://drupal.org/node/2068471 is adding support so we can tell
-      // if this is called on a 404/403 page.
-      // Check if the active trail has been overridden for this menu tree.
-      $active_path = $this->getPath($menu_name);
-      // Find a menu link corresponding to the current path. If
-      // $active_path is NULL, let menu_link_get_preferred() determine
-      // the path.
-      if ($active_link = $this->menuLinkGetPreferred($menu_name, $active_path)) {
-        if ($active_link['menu_name'] == $menu_name) {
-          // Use all the coordinates, except the last one because
-          // there can be no child beyond the last column.
-          for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
-            if ($active_link['p' . $i]) {
-              $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
-            }
-          }
-        }
-      }
-    }
-    return $active_trail;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setPath($menu_name, $path = NULL) {
-    if (isset($path)) {
-      $this->trailPaths[$menu_name] = $path;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getPath($menu_name) {
-    return isset($this->trailPaths[$menu_name]) ? $this->trailPaths[$menu_name] : NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function renderMenu($menu_name) {
-
-    if (!isset($this->menuOutput[$menu_name])) {
-      $tree = $this->buildPageData($menu_name);
-      $this->menuOutput[$menu_name] = $this->renderTree($tree);
-    }
-    return $this->menuOutput[$menu_name];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function renderTree($tree) {
-    $build = array();
-    $items = array();
-    $menu_name = $tree ? end($tree)['link']['menu_name'] : '';
-
-    // Pull out just the menu links we are going to render so that we
-    // get an accurate count for the first/last classes.
-    foreach ($tree as $data) {
-      if ($data['link']['access'] && !$data['link']['hidden']) {
-        $items[] = $data;
-      }
-    }
-
-    foreach ($items as $data) {
-      $class = array();
-      // Set a class for the <li>-tag. Since $data['below'] may contain local
-      // tasks, only set 'expanded' class if the link also has children within
-      // the current menu.
-      if ($data['link']['has_children'] && $data['below']) {
-        $class[] = 'expanded';
-      }
-      elseif ($data['link']['has_children']) {
-        $class[] = 'collapsed';
-      }
-      else {
-        $class[] = 'leaf';
-      }
-      // Set a class if the link is in the active trail.
-      if ($data['link']['in_active_trail']) {
-        $class[] = 'active-trail';
-        $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
-      }
-
-      // Allow menu-specific theme overrides.
-      $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
-      $element['#attributes']['class'] = $class;
-      $element['#title'] = $data['link']['title'];
-      // @todo Use route name and parameters to generate the link path, unless
-      //    it is external.
-      $element['#href'] = $data['link']['link_path'];
-      $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
-      $element['#below'] = $data['below'] ? $this->renderTree($data['below']) : $data['below'];
-      $element['#original_link'] = $data['link'];
-      // Index using the link's unique mlid.
-      $build[$data['link']['mlid']] = $element;
-    }
-    if ($build) {
-      // Make sure drupal_render() does not re-order the links.
-      $build['#sorted'] = TRUE;
-      // Add the theme wrapper for outer markup.
-      // Allow menu-specific theme overrides.
-      $build['#theme_wrappers'][] = 'menu_tree__' . strtr($menu_name, '-', '_');
-      // Set cache tag.
-      $menu_name = $data['link']['menu_name'];
-      $build['#cache']['tags']['menu'][$menu_name] = $menu_name;
-    }
-
-    return $build;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildTree($menu_name, array $parameters = array()) {
-    // Build the menu tree.
-    $tree = $this->doBuildTree($menu_name, $parameters);
-    // Check access for the current user to each item in the tree.
-    $this->checkAccess($tree);
-    return $tree;
-  }
-
-  /**
-   * Builds a menu tree.
-   *
-   * This function may be used build the data for a menu tree only, for example
-   * to further massage the data manually before further processing happens.
-   * MenuTree::checkAccess() needs to be invoked afterwards.
-   *
-   * @param string $menu_name
-   *   The name of the menu.
-   * @param array $parameters
-   *   The parameters passed into static::buildTree()
-   *
-   * @see static::buildTree()
-   */
-  protected function doBuildTree($menu_name, array $parameters = array()) {
-    $language_interface = $this->languageManager->getCurrentLanguage();
-
-    // Build the cache id; sort parents to prevent duplicate storage and remove
-    // default parameter values.
-    if (isset($parameters['expanded'])) {
-      sort($parameters['expanded']);
-    }
-    $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
-
-    // If we do not have this tree in the static cache, check {cache_menu}.
-    if (!isset($this->menuTree[$tree_cid])) {
-      $cache = $this->cache->get($tree_cid);
-      if ($cache && $cache->data) {
-        $this->menuTree[$tree_cid] = $cache->data;
-      }
-    }
-
-    if (!isset($this->menuTree[$tree_cid])) {
-      $query = $this->queryFactory->get('menu_link');
-      for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
-        $query->sort('p' . $i, 'ASC');
-      }
-      $query->condition('menu_name', $menu_name);
-      if (!empty($parameters['expanded'])) {
-        $query->condition('plid', $parameters['expanded'], 'IN');
-      }
-      elseif (!empty($parameters['only_active_trail'])) {
-        $query->condition('mlid', $parameters['active_trail'], 'IN');
-      }
-      $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
-      if ($min_depth != 1) {
-        $query->condition('depth', $min_depth, '>=');
-      }
-      if (isset($parameters['max_depth'])) {
-        $query->condition('depth', $parameters['max_depth'], '<=');
-      }
-      // Add custom query conditions, if any were passed.
-      if (isset($parameters['conditions'])) {
-        foreach ($parameters['conditions'] as $column => $value) {
-          $query->condition($column, $value);
-        }
-      }
-
-      // Build an ordered array of links using the query result object.
-      $links = array();
-      if ($result = $query->execute()) {
-        $links = $this->menuLinkStorage->loadMultiple($result);
-      }
-      $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
-      $tree = $this->doBuildTreeData($links, $active_trail, $min_depth);
-
-      // Cache the data, if it is not already in the cache.
-      $this->cache->set($tree_cid, $tree, Cache::PERMANENT, array('menu' => $menu_name));
-      $this->menuTree[$tree_cid] = $tree;
-    }
-
-    return $this->menuTree[$tree_cid];
-  }
-
-  /**
-   * Sorts the menu tree and recursively checks access for each item.
-   *
-   * @param array $tree
-   *   The menu tree you wish to operate on.
-   */
-  protected function checkAccess(&$tree) {
-    $new_tree = array();
-    foreach ($tree as $key => $v) {
-      $item = &$tree[$key]['link'];
-      $this->menuLinkTranslate($item);
-      if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
-        if ($tree[$key]['below']) {
-          $this->checkAccess($tree[$key]['below']);
-        }
-        // The weights are made a uniform 5 digits by adding 50000 as an offset.
-        // After _menu_link_translate(), $item['title'] has the localized link
-        // title. Adding the mlid to the end of the index insures that it is
-        // unique.
-        $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
-      }
-    }
-    // Sort siblings in the tree based on the weights and localized titles.
-    ksort($new_tree);
-    $tree = $new_tree;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildTreeData(array $links, array $parents = array(), $depth = 1) {
-    $tree = $this->doBuildTreeData($links, $parents, $depth);
-    $this->checkAccess($tree);
-    return $tree;
-  }
-
-  /**
-   * Prepares the data for calling $this->treeDataRecursive().
-   */
-  protected function doBuildTreeData(array $links, array $parents = array(), $depth = 1) {
-    // Reverse the array so we can use the more efficient array_pop() function.
-    $links = array_reverse($links);
-    return $this->treeDataRecursive($links, $parents, $depth);
-  }
-
-  /**
-   * Builds the data representing a menu tree.
-   *
-   * The function is a bit complex because the rendering of a link depends on
-   * the next menu link.
-   *
-   * @param array $links
-   *   A flat array of menu links that are part of the menu. Each array element
-   *   is an associative array of information about the menu link, containing
-   *   the fields from the {menu_links} table, and optionally additional
-   *   information from the {menu_router} table, if the menu item appears in
-   *   both tables. This array must be ordered depth-first.
-   *   See _menu_build_tree() for a sample query.
-   * @param array $parents
-   *   An array of the menu link ID values that are in the path from the current
-   *   page to the root of the menu tree.
-   * @param int $depth
-   *   The minimum depth to include in the returned menu tree.
-   *
-   * @return array
-   */
-  protected function treeDataRecursive(&$links, $parents, $depth) {
-    $tree = array();
-    while ($item = array_pop($links)) {
-      // We need to determine if we're on the path to root so we can later build
-      // the correct active trail.
-      $item['in_active_trail'] = in_array($item['mlid'], $parents);
-      // Add the current link to the tree.
-      $tree[$item['mlid']] = array(
-        'link' => $item,
-        'below' => array(),
-      );
-      // Look ahead to the next link, but leave it on the array so it's
-      // available to other recursive function calls if we return or build a
-      // sub-tree.
-      $next = end($links);
-      // Check whether the next link is the first in a new sub-tree.
-      if ($next && $next['depth'] > $depth) {
-        // Recursively call doBuildTreeData to build the sub-tree.
-        $tree[$item['mlid']]['below'] = $this->treeDataRecursive($links, $parents, $next['depth']);
-        // Fetch next link after filling the sub-tree.
-        $next = end($links);
-      }
-      // Determine if we should exit the loop and return.
-      if (!$next || $next['depth'] < $depth) {
-        break;
-      }
-    }
-    return $tree;
-  }
-
-  /**
-   * Wraps menu_link_get_preferred().
-   */
-  protected function menuLinkGetPreferred($menu_name, $active_path) {
-    return menu_link_get_preferred($active_path, $menu_name);
-  }
-
-  /**
-   * Wraps _menu_link_translate().
-   */
-  protected function menuLinkTranslate(&$item) {
-    _menu_link_translate($item);
-  }
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeInterface.php
deleted file mode 100644
index 418f602..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeInterface.php
+++ /dev/null
@@ -1,182 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuTreeInterface.
- */
-
-namespace Drupal\menu_link;
-
-/**
- * Defines an interface for trees out of menu links.
- */
-interface MenuTreeInterface {
-
-  /**
-   * Returns a rendered menu tree.
-   *
-   * The menu item's LI element is given one of the following classes:
-   * - expanded: The menu item is showing its submenu.
-   * - collapsed: The menu item has a submenu which is not shown.
-   * - leaf: The menu item has no submenu.
-   *
-   * @param array $tree
-   *   A data structure representing the tree as returned from menu_tree_data.
-   *
-   * @return array
-   *   A structured array to be rendered by drupal_render().
-   */
-  public function renderTree($tree);
-
-  /**
-   * Sets the path for determining the active trail of the specified menu tree.
-   *
-   * This path will also affect the breadcrumbs under some circumstances.
-   * Breadcrumbs are built using the preferred link returned by
-   * menu_link_get_preferred(). If the preferred link is inside one of the menus
-   * specified in calls to static::setPath(), the preferred link will be
-   * overridden by the corresponding path returned by static::getPath().
-   *
-   * @param string $menu_name
-   *   The name of the affected menu tree.
-   * @param string $path
-   *   The path to use when finding the active trail.
-   */
-  public function setPath($menu_name, $path = NULL);
-
-  /**
-   * Gets the path for determining the active trail of the specified menu tree.
-   *
-   * @param string $menu_name
-   *   The menu name of the requested tree.
-   *
-   * @return string
-   *   A string containing the path. If no path has been specified with
-   *   static::setPath(), NULL is returned.
-   */
-  public function getPath($menu_name);
-
-  /**
-   * Gets the active trail IDs of the specified menu tree.
-   *
-   * @param string $menu_name
-   *   The menu name of the requested tree.
-   *
-   * @return array
-   *   An array containing the active trail: a list of mlids.
-   */
-  public function getActiveTrailIds($menu_name);
-
-  /**
-   * Sorts and returns the built data representing a menu tree.
-   *
-   * @param array $links
-   *   A flat array of menu links that are part of the menu. Each array element
-   *   is an associative array of information about the menu link, containing
-   *   the fields from the {menu_links} table, and optionally additional
-   *   information from the {menu_router} table, if the menu item appears in
-   *   both tables. This array must be ordered depth-first.
-   *   See _menu_build_tree() for a sample query.
-   * @param array $parents
-   *   An array of the menu link ID values that are in the path from the current
-   *   page to the root of the menu tree.
-   * @param int $depth
-   *   The minimum depth to include in the returned menu tree.
-   *
-   * @return array
-   *   An array of menu links in the form of a tree. Each item in the tree is an
-   *   associative array containing:
-   *   - link: The menu link item from $links, with additional element
-   *     'in_active_trail' (TRUE if the link ID was in $parents).
-   *   - below: An array containing the sub-tree of this item, where each
-   *     element is a tree item array with 'link' and 'below' elements. This
-   *     array will be empty if the menu item has no items in its sub-tree
-   *     having a depth greater than or equal to $depth.
-   */
-  public function buildTreeData(array $links, array $parents = array(), $depth = 1);
-
-  /**
-   * Gets the data structure for a named menu tree, based on the current page.
-   *
-   * The tree order is maintained by storing each parent in an individual
-   * field, see http://drupal.org/node/141866 for more.
-   *
-   * @param string $menu_name
-   *   The named menu links to return.
-   * @param int $max_depth
-   *   (optional) The maximum depth of links to retrieve.
-   * @param bool $only_active_trail
-   *   (optional) Whether to only return the links in the active trail (TRUE)
-   *   instead of all links on every level of the menu link tree (FALSE).
-   *   Defaults to FALSE.
-   *
-   * @return array
-   *   An array of menu links, in the order they should be rendered. The array
-   *   is a list of associative arrays -- these have two keys, link and below.
-   *   link is a menu item, ready for theming as a link. Below represents the
-   *   submenu below the link if there is one, and it is a subtree that has the
-   *   same structure described for the top-level array.
-   */
-  public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE);
-
-  /**
-   * Gets the data structure representing a named menu tree.
-   *
-   * Since this can be the full tree including hidden items, the data returned
-   * may be used for generating an an admin interface or a select.
-   *
-   * @param string $menu_name
-   *   The named menu links to return
-   * @param array $link
-   *   A fully loaded menu link, or NULL. If a link is supplied, only the
-   *   path to root will be included in the returned tree - as if this link
-   *   represented the current page in a visible menu.
-   * @param int $max_depth
-   *   Optional maximum depth of links to retrieve. Typically useful if only one
-   *   or two levels of a sub tree are needed in conjunction with a non-NULL
-   *   $link, in which case $max_depth should be greater than $link['depth'].
-   *
-   * @return array
-   *   An tree of menu links in an array, in the order they should be rendered.
-   */
-  public function buildAllData($menu_name, $link = NULL, $max_depth = NULL);
-
-  /**
-   * Renders a menu tree based on the current path.
-   *
-   * @param string $menu_name
-   *   The name of the menu.
-   *
-   * @return array
-   *   A structured array representing the specified menu on the current page,
-   *   to be rendered by drupal_render().
-   */
-  public function renderMenu($menu_name);
-
-  /**
-   * Builds a menu tree, translates links, and checks access.
-   *
-   * @param string $menu_name
-   *   The name of the menu.
-   * @param array $parameters
-   *   (optional) An associative array of build parameters. Possible keys:
-   *   - expanded: An array of parent link ids to return only menu links that
-   *     are children of one of the plids in this list. If empty, the whole menu
-   *     tree is built, unless 'only_active_trail' is TRUE.
-   *   - active_trail: An array of mlids, representing the coordinates of the
-   *     currently active menu link.
-   *   - only_active_trail: Whether to only return links that are in the active
-   *     trail. This option is ignored, if 'expanded' is non-empty.
-   *   - min_depth: The minimum depth of menu links in the resulting tree.
-   *     Defaults to 1, which is the default to build a whole tree for a menu
-   *     (excluding menu container itself).
-   *   - max_depth: The maximum depth of menu links in the resulting tree.
-   *   - conditions: An associative array of custom database select query
-   *     condition key/value pairs; see _menu_build_tree() for the actual query.
-   *
-   * @return array
-   *   A fully built menu tree.
-   */
-  public function buildTree($menu_name, array $parameters = array());
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/StaticMenuLinks.php b/core/modules/menu_link/lib/Drupal/menu_link/StaticMenuLinks.php
deleted file mode 100644
index fbbac4a..0000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/StaticMenuLinks.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Menu\StaticMenuLinks.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Component\Discovery\YamlDiscovery;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-
-/**
- * Provides a service which finds and alters default menu links in yml files.
- */
-class StaticMenuLinks {
-
-  /**
-   * The module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
-   */
-  protected $moduleHandler;
-
-  /**
-   * Constructs a new StaticMenuLinks.
-   *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler.
-   */
-  public function __construct(ModuleHandlerInterface $module_handler) {
-    $this->moduleHandler = $module_handler;
-  }
-
-  /**
-   * Gets the menu links defined in YAML files.
-   *
-   * @return array
-   *   An array of default menu links.
-   */
-  public function getLinks() {
-    $discovery = $this->getDiscovery();
-    foreach ($discovery->findAll() as $module => $menu_links) {
-      foreach ($menu_links as $machine_name => $menu_link) {
-        $all_links[$machine_name] = $menu_link;
-        $all_links[$machine_name]['machine_name'] = $machine_name;
-        $all_links[$machine_name]['module'] = $module;
-      }
-    }
-
-    $this->moduleHandler->alter('menu_link_defaults', $all_links);
-    // Change the key to match the DB column for now.
-    foreach ($all_links as $machine_name => $menu_link) {
-      $all_links[$machine_name]['link_title'] = $all_links[$machine_name]['title'];
-      unset($all_links[$machine_name]['title']);
-    }
-
-    return $all_links;
-  }
-
-  /**
-   * Creates a YAML discovery for menu links.
-   *
-   * @return \Drupal\Component\Discovery\YamlDiscovery
-   *   An YAML discovery instance.
-   */
-  protected function getDiscovery() {
-    return new YamlDiscovery('menu_links', $this->moduleHandler->getModuleDirectories());
-  }
-
-}
-
diff --git a/core/modules/menu_link/menu_link.api.php b/core/modules/menu_link/menu_link.api.php
deleted file mode 100644
index b4e8892..0000000
--- a/core/modules/menu_link/menu_link.api.php
+++ /dev/null
@@ -1,165 +0,0 @@
-<?php
-
-/**
- * @file
- * Hooks provided by the Menu link module.
- */
-
-/**
- * @addtogroup hooks
- * @{
- */
-
-/**
- * Alter a menu link after it has been translated and before it is rendered.
- *
- * This hook is invoked from _menu_link_translate() after a menu link has been
- * translated; i.e., after the user access to the link's target page has
- * been checked. It is only invoked if $menu_link['options']['alter'] has been
- * set to a non-empty value (e.g. TRUE). This flag should be set using
- * hook_menu_link_presave().
- *
- * Implementations of this hook are able to alter any property of the menu link.
- * For example, this hook may be used to add a page-specific query string to all
- * menu links, or hide a certain link by setting:
- * @code
- *   'hidden' => 1,
- * @endcode
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- *
- * @see hook_menu_link_alter()
- */
-function hook_translated_menu_link_alter(\Drupal\menu_link\Entity\MenuLink &$menu_link, $map) {
-  if ($menu_link->href == 'devel/cache/clear') {
-    $menu_link->localized_options['query'] = drupal_get_destination();
-  }
-}
-
-/**
- * Alter menu links when loaded and before they are rendered.
- *
- * This hook is only invoked if $menu_link->options['alter'] has been set to a
- * non-empty value (e.g., TRUE). This flag should be set using
- * hook_menu_link_presave().
- * @ todo The paragraph above is lying! This hasn't been (re)implemented yet.
- *
- * Implementations of this hook are able to alter any property of the menu link.
- * For example, this hook may be used to add a page-specific query string to all
- * menu links, or hide a certain link by setting:
- * @code
- *   'hidden' => 1,
- * @endcode
- *
- * @param array $menu_links
- *   An array of menu link entities.
- *
- * @see hook_menu_link_presave()
- */
-function hook_menu_link_load($menu_links) {
-  foreach ($menu_links as $menu_link) {
-    if ($menu_link->href == 'devel/cache/clear') {
-      $menu_link->options['query'] = drupal_get_destination();
-    }
-  }
-}
-
-
-/**
- * Alter the data of a menu link entity before it is created or updated.
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- *
- * @see hook_menu_link_load()
- */
-function hook_menu_link_presave(\Drupal\menu_link\Entity\MenuLink $menu_link) {
-  // Make all new admin links hidden (a.k.a disabled).
-  if (strpos($menu_link->link_path, 'admin') === 0 && $menu_link->isNew()) {
-    $menu_link->hidden = 1;
-  }
-  // Flag a link to be altered by hook_menu_link_load().
-  if ($menu_link->link_path == 'devel/cache/clear') {
-    $menu_link->options['alter'] = TRUE;
-  }
-  // Flag a menu link to be altered by hook_menu_link_load(), but only if it is
-  // derived from a menu router item; i.e., do not alter a custom menu link
-  // pointing to the same path that has been created by a user.
-  if ($menu_link->machine_name == 'user.page') {
-    $menu_link->options['alter'] = TRUE;
-  }
-}
-
-/**
- * Inform modules that a menu link has been created.
- *
- * This hook is used to notify modules that menu links have been
- * created. Contributed modules may use the information to perform
- * actions based on the information entered into the menu system.
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- *
- * @see hook_menu_link_presave()
- * @see hook_menu_link_update()
- * @see hook_menu_link_delete()
- */
-function hook_menu_link_insert(\Drupal\menu_link\Entity\MenuLink $menu_link) {
-  // In our sample case, we track menu items as editing sections
-  // of the site. These are stored in our table as 'disabled' items.
-  $record['mlid'] = $menu_link->id();
-  $record['menu_name'] = $menu_link->menu_name;
-  $record['status'] = 0;
-  db_insert('menu_example')->fields($record)->execute();
-}
-
-/**
- * Inform modules that a menu link has been updated.
- *
- * This hook is used to notify modules that menu items have been
- * updated. Contributed modules may use the information to perform
- * actions based on the information entered into the menu system.
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- *
- * @see hook_menu_link_presave()
- * @see hook_menu_link_insert()
- * @see hook_menu_link_delete()
- */
-function hook_menu_link_update(\Drupal\menu_link\Entity\MenuLink $menu_link) {
-  // If the parent menu has changed, update our record.
-  $menu_name = db_query("SELECT menu_name FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $menu_link->id()))->fetchField();
-  if ($menu_name != $menu_link->menu_name) {
-    db_update('menu_example')
-      ->fields(array('menu_name' => $menu_link->menu_name))
-      ->condition('mlid', $menu_link->id())
-      ->execute();
-  }
-}
-
-/**
- * Inform modules that a menu link has been deleted.
- *
- * This hook is used to notify modules that menu links have been
- * deleted. Contributed modules may use the information to perform
- * actions based on the information entered into the menu system.
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- *
- * @see hook_menu_link_presave()
- * @see hook_menu_link_insert()
- * @see hook_menu_link_update()
- */
-function hook_menu_link_delete(\Drupal\menu_link\Entity\MenuLink $menu_link) {
-  // Delete the record from our table.
-  db_delete('menu_example')
-    ->condition('mlid', $menu_link->id())
-    ->execute();
-}
-
-/**
- * @} End of "addtogroup hooks".
- */
diff --git a/core/modules/menu_link/menu_link.info.yml b/core/modules/menu_link/menu_link.info.yml
deleted file mode 100644
index bc8e62b..0000000
--- a/core/modules/menu_link/menu_link.info.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-name: Menu Link
-type: module
-description: Provides menu links.
-package: Core
-version: VERSION
-core: 8.x
-# @todo Menu links functionality has been moved from system.module and menu.inc
-# to this module, so make it required until everything is moved over.
-required: TRUE
diff --git a/core/modules/menu_link/menu_link.install b/core/modules/menu_link/menu_link.install
deleted file mode 100644
index c789d34..0000000
--- a/core/modules/menu_link/menu_link.install
+++ /dev/null
@@ -1,221 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions for the menu_link module.
- */
-
-/**
- * Implements hook_schema().
- */
-function menu_link_schema() {
-  $schema['menu_links'] = array(
-    'description' => 'Contains the individual links within a menu.',
-    'fields' => array(
-     'menu_name' => array(
-        'description' => "The menu name. All links with the same menu name (such as 'tools') are part of the same menu.",
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'mlid' => array(
-        'description' => 'The menu link ID (mlid) is the integer primary key.',
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'uuid' => array(
-        'description' => 'Unique Key: Universally unique identifier for this entity.',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => FALSE,
-      ),
-      'machine_name' => array(
-        'description' => 'Unique machine name: Optional human-readable ID for this link.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => FALSE,
-      ),
-      'plid' => array(
-        'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'link_path' => array(
-        'description' => 'The Drupal path or external path this link points to.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of this link.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'link_title' => array(
-        'description' => 'The text displayed for the link.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'options' => array(
-        'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
-        'type' => 'blob',
-        'not null' => FALSE,
-        'serialize' => TRUE,
-      ),
-      'module' => array(
-        'description' => 'The name of the module that generated this link.',
-        'type' => 'varchar',
-        'length' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
-        'not null' => TRUE,
-        'default' => 'system',
-      ),
-      'hidden' => array(
-        'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'external' => array(
-        'description' => 'A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'has_children' => array(
-        'description' => 'Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'expanded' => array(
-        'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'weight' => array(
-        'description' => 'Link weight among links in the same menu at the same depth.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'depth' => array(
-        'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'customized' => array(
-        'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'p1' => array(
-        'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p2' => array(
-        'description' => 'The second mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p3' => array(
-        'description' => 'The third mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p4' => array(
-        'description' => 'The fourth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p5' => array(
-        'description' => 'The fifth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p6' => array(
-        'description' => 'The sixth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p7' => array(
-        'description' => 'The seventh mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p8' => array(
-        'description' => 'The eighth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p9' => array(
-        'description' => 'The ninth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'updated' => array(
-        'description' => 'Flag that indicates that this link was generated during the update from Drupal 5.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'route_name' => array(
-        'description' => 'The machine name of a defined Symfony Route this menu item represents.',
-        'type' => 'varchar',
-        'length' => 255,
-      ),
-      'route_parameters' => array(
-        'description' => 'Serialized array of route parameters of this menu link.',
-        'type' => 'blob',
-        'size' => 'big',
-        'not null' => FALSE,
-        'serialize' => TRUE,
-      ),
-    ),
-    'indexes' => array(
-      'path_menu' => array(array('link_path', 128), 'menu_name'),
-      'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
-      'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
-    ),
-    'primary key' => array('mlid'),
-  );
-
-  return $schema;
-}
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
deleted file mode 100644
index ee273d8..0000000
--- a/core/modules/menu_link/menu_link.module
+++ /dev/null
@@ -1,215 +0,0 @@
-<?php
-
-/**
- * @file
- * Enables users to create menu links.
- */
-
-use Drupal\Core\Url;
-use Drupal\menu_link\Entity\MenuLink;
-use Drupal\menu_link\MenuLinkInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
-
-function menu_link_help($route_name, Request $request) {
-  switch ($route_name) {
-    case 'help.page.menu_link':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Menu Link module allows users to create menu links. It is required by the Menu UI module, which provides an interface for managing menus. See the <a href="!menu-help">Menu UI module help page</a> for more information.', array('!menu-help' => \Drupal::url('help.page', array('name' => 'menu_ui')))) . '</p>';
-      return $output;
-  }
-}
-
-/**
- * Entity URI callback.
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- */
-function menu_link_uri(MenuLink $menu_link) {
-  return new Url($menu_link->route_name, $menu_link->route_parameters);
-}
-
-/**
- * Loads a menu link entity.
- *
- * This function should never be called from within node_load() or any other
- * function used as a menu object load function since an infinite recursion may
- * occur.
- *
- * @param int $mlid
- *   The menu link ID.
- * @param bool $reset
- *   (optional) Whether to reset the menu_link_load_multiple() cache.
- *
- * @return \Drupal\menu_link\Entity\MenuLink|null
- *   A menu link entity, or NULL if there is no entity with the given ID.
- */
-function menu_link_load($mlid = NULL, $reset = FALSE) {
-  return entity_load('menu_link', $mlid, $reset);
-}
-
-/**
- * Loads menu link entities from the database.
- *
- * @param array $mlids
- *   (optional) An array of entity IDs. If omitted, all entities are loaded.
- * @param bool $reset
- *   (optional) Whether to reset the internal cache.
- *
- * @return array<\Drupal\menu_link\Entity\MenuLink>
- *   An array of menu link entities indexed by entity IDs.
- *
- * @see menu_link_load()
- * @see entity_load_multiple()
- */
-function menu_link_load_multiple(array $mlids = NULL, $reset = FALSE) {
-  return entity_load_multiple('menu_link', $mlids, $reset);
-}
-
-/**
- * Deletes a menu link.
- *
- * @param int $mlid
- *   The menu link ID.
- *
- * @see menu_link_delete_multiple()
- */
-function menu_link_delete($mlid) {
-  menu_link_delete_multiple(array($mlid));
-}
-
-/**
- * Deletes multiple menu links.
- *
- * @param array $mlids
- *   An array of menu link IDs.
- * @param bool $force
- *   (optional) Forces deletion. Internal use only, setting to TRUE is
- *   discouraged. Defaults to FALSE.
- * @param bool $prevent_reparenting
- *   (optional) Disables the re-parenting logic from the deletion process.
- *   Defaults to FALSE.
- */
-function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_reparenting = FALSE) {
-  if (!$mlids) {
-    // If no IDs or invalid IDs were passed, do nothing.
-    return;
-  }
-
-  $controller = \Drupal::entityManager()
-    ->getStorage('menu_link');
-  if (!$force) {
-    $entity_query = \Drupal::entityQuery('menu_link');
-    $group = $entity_query->orConditionGroup()
-      ->condition('module', 'system', '<>')
-      ->condition('updated', 0, '<>');
-
-    $entity_query->condition('mlid', $mlids, 'IN');
-    $entity_query->condition($group);
-
-    $result = $entity_query->execute();
-    $entities = $controller->loadMultiple($result);
-  }
-  else {
-    $entities = $controller->loadMultiple($mlids);
-  }
-  $controller->setPreventReparenting($prevent_reparenting);
-  $controller->delete($entities);
-}
-
-/**
- * Saves a menu link.
- *
- * After calling this function, rebuild the menu cache using
- * menu_cache_clear_all().
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   The menu link entity to be saved.
- *
- * @return int|bool
- *   Returns SAVED_NEW or SAVED_UPDATED if the save operation succeeded, or
- *   FALSE if it failed.
- */
-function menu_link_save(MenuLink $menu_link) {
-  return $menu_link->save();
-}
-
-/**
- * Inserts, updates, enables, disables, or deletes an uncustomized menu link.
- *
- * @param string $module
- *   The name of the module that owns the link.
- * @param string $op
- *   Operation to perform: insert, update, enable, disable, or delete.
- * @param string $link_path
- *   The path this link points to.
- * @param string $link_title
- *   (optional) Title of the link to insert or new title to update the link to.
- *   Unused for delete. Defaults to NULL.
- *
- * @return integer|null
- *   The insert op returns the mlid of the new item. Others op return NULL.
- */
-function menu_link_maintain($module, $op, $link_path, $link_title = NULL) {
-  $menu_link_controller = \Drupal::entityManager()
-    ->getStorage('menu_link');
-  switch ($op) {
-    case 'insert':
-      $menu_link = entity_create('menu_link', array(
-        'link_title' => $link_title,
-        'link_path' => $link_path,
-        'module' => $module,)
-      );
-      return $menu_link->save();
-
-    case 'update':
-      $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path, 'module' => $module, 'customized' => 0));
-      foreach ($menu_links as $menu_link) {
-        $menu_link->original = clone $menu_link;
-        if (isset($link_title)) {
-          $menu_link->link_title = $link_title;
-        }
-        $menu_link_controller->save($menu_link);
-      }
-      break;
-
-    case 'enable':
-    case 'disable':
-      $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path, 'module' => $module, 'customized' => 0));
-      foreach ($menu_links as $menu_link) {
-        $menu_link->original = clone $menu_link;
-        $menu_link->hidden = ($op == 'disable' ? 1 : 0);
-        $menu_link->customized = 1;
-        if (isset($link_title)) {
-          $menu_link->link_title = $link_title;
-        }
-        $menu_link_controller->save($menu_link);
-      }
-      break;
-
-    case 'delete':
-      $result = \Drupal::entityQuery('menu_link')->condition('link_path', $link_path)->execute();
-      if (!empty($result)) {
-        menu_link_delete_multiple($result);
-      }
-      break;
-  }
-}
-
-/**
- * Implements hook_system_breadcrumb_alter().
- */
-function menu_link_system_breadcrumb_alter(array &$breadcrumb, array $attributes, array $context) {
-  // Custom breadcrumb behavior for editing menu links, we append a link to
-  // the menu in which the link is found.
-  if (!empty($attributes[RouteObjectInterface::ROUTE_NAME]) && $attributes[RouteObjectInterface::ROUTE_NAME] == 'menu_ui.link_edit' && !empty($attributes['menu_link'])) {
-    $menu_link = $attributes['menu_link'];
-    if (($menu_link instanceof MenuLinkInterface) && !$menu_link->isNew()) {
-      // Add a link to the menu admin screen.
-      $menu = entity_load('menu', $menu_link->menu_name);
-      $breadcrumb[] = Drupal::l($menu->label(), 'menu_ui.menu_edit', array('menu' => $menu->id));
-    }
-  }
-}
diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml
deleted file mode 100644
index 88f5037..0000000
--- a/core/modules/menu_link/menu_link.services.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-services:
-  menu_link.tree:
-    class: Drupal\menu_link\MenuTree
-    arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state']
-  menu_link.static:
-    class: Drupal\menu_link\StaticMenuLinks
-    arguments: ['@module_handler']
diff --git a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php
deleted file mode 100644
index bd77a45..0000000
--- a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php
+++ /dev/null
@@ -1,554 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\Tests\MenuTreeTest.
- */
-
-namespace Drupal\menu_link\Tests {
-
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Language\Language;
-use Drupal\menu_link\MenuTree;
-use Drupal\Tests\UnitTestCase;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\RequestStack;
-
-if (!defined('MENU_MAX_DEPTH')) {
-  define('MENU_MAX_DEPTH', 9);
-}
-
-/**
- * Tests the menu tree.
- *
- * @group Drupal
- * @group menu_link
- *
- * @coversDefaultClass \Drupal\menu_link\MenuTree
- */
-class MenuTreeTest extends UnitTestCase {
-
-  /**
-   * The tested menu tree.
-   *
-   * @var \Drupal\menu_link\MenuTree|\Drupal\menu_link\Tests\TestMenuTree
-   */
-  protected $menuTree;
-
-  /**
-   * The mocked database connection.
-   *
-   * @var \Drupal\Core\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $connection;
-
-  /**
-   * The mocked cache backend.
-   *
-   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $cacheBackend;
-
-  /**
-   * The mocked language manager.
-   *
-   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $languageManager;
-
-  /**
-   * The test request stack.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack.
-   */
-  protected $requestStack;
-
-  /**
-   * The mocked entity manager.
-   *
-   * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $entityManager;
-
-  /**
-   * The mocked entity query factor.y
-   *
-   * @var  \Drupal\Core\Entity\Query\QueryFactory|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $entityQueryFactory;
-
-  /**
-   * The mocked state.
-   *
-   * @var \Drupal\Core\State\StateInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $state;
-
-  /**
-   * Stores some default values for a menu link.
-   *
-   * @var array
-   */
-  protected $defaultMenuLink = array(
-    'menu_name' => 'main-menu',
-    'mlid' => 1,
-    'title' => 'Example 1',
-    'route_name' => 'example1',
-    'link_path' => 'example1',
-    'access' => 1,
-    'hidden' => FALSE,
-    'has_children' => FALSE,
-    'in_active_trail' => TRUE,
-    'localized_options' => array('attributes' => array('title' => '')),
-    'weight' => 0,
-  );
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Tests \Drupal\menu_link\MenuTree',
-      'description' => '',
-      'group' => 'Menu',
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
-    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
-    $this->requestStack = new RequestStack();
-    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
-    $this->entityQueryFactory = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryFactory')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $this->state = $this->getMock('Drupal\Core\State\StateInterface');
-
-    $this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state);
-  }
-
-  /**
-   * Tests active paths.
-   *
-   * @covers ::setPath
-   * @covers ::getPath
-   */
-  public function testActivePaths() {
-    $this->assertNull($this->menuTree->getPath('test_menu1'));
-
-    $this->menuTree->setPath('test_menu1', 'example_path1');
-    $this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1'));
-    $this->assertNull($this->menuTree->getPath('test_menu2'));
-
-    $this->menuTree->setPath('test_menu2', 'example_path2');
-    $this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1'));
-    $this->assertEquals('example_path2', $this->menuTree->getPath('test_menu2'));
-  }
-
-  /**
-   * Tests buildTreeData with a single level.
-   *
-   * @covers ::buildTreeData
-   * @covers ::doBuildTreeData
-   */
-  public function testBuildTreeDataWithSingleLevel() {
-    $items = array();
-    $items[] = array(
-      'mlid' => 1,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example1',
-      'access' => TRUE,
-    );
-    $items[] = array(
-      'mlid' => 2,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example2',
-      'access' => TRUE,
-    );
-
-    $result = $this->menuTree->buildTreeData($items, array(), 1);
-
-    $this->assertCount(2, $result);
-    $result1 = array_shift($result);
-    $this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']);
-    $result2 = array_shift($result);
-    $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']);
-  }
-
-  /**
-   * Tests buildTreeData with a single level and one item being active.
-   *
-   * @covers ::buildTreeData
-   * @covers ::doBuildTreeData
-   */
-  public function testBuildTreeDataWithSingleLevelAndActiveItem() {
-    $items = array();
-    $items[] = array(
-      'mlid' => 1,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example1',
-      'access' => TRUE,
-    );
-    $items[] = array(
-      'mlid' => 2,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example2',
-      'access' => TRUE,
-    );
-
-    $result = $this->menuTree->buildTreeData($items, array(1), 1);
-
-    $this->assertCount(2, $result);
-    $result1 = array_shift($result);
-    $this->assertEquals($items[0] + array('in_active_trail' => TRUE), $result1['link']);
-    $result2 = array_shift($result);
-    $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']);
-  }
-
-  /**
-   * Tests buildTreeData with a single level and none item being active.
-   *
-   * @covers ::buildTreeData
-   * @covers ::doBuildTreeData
-   */
-  public function testBuildTreeDataWithSingleLevelAndNoActiveItem() {
-    $items = array();
-    $items[] = array(
-      'mlid' => 1,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example1',
-      'access' => TRUE,
-    );
-    $items[] = array(
-      'mlid' => 2,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example2',
-      'access' => TRUE,
-    );
-
-    $result = $this->menuTree->buildTreeData($items, array(3), 1);
-
-    $this->assertCount(2, $result);
-    $result1 = array_shift($result);
-    $this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']);
-    $result2 = array_shift($result);
-    $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']);
-  }
-
-  /**
-   * Tests buildTreeData with a more complex example.
-   *
-   * @covers ::buildTreeData
-   * @covers ::doBuildTreeData
-   */
-  public function testBuildTreeWithComplexData() {
-    $items = array(
-      1 => array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      2 => array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      3 => array('mlid' => 3, 'depth' => 2, 'route_name' => 'example3', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      4 => array('mlid' => 4, 'depth' => 3, 'route_name' => 'example4', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      5 => array('mlid' => 5, 'depth' => 1, 'route_name' => 'example5', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-    );
-
-    $tree = $this->menuTree->buildTreeData($items);
-
-    // Validate that parent items #1, #2, and #5 exist on the root level.
-    $this->assertEquals($items[1]['mlid'], $tree['50000  1']['link']['mlid']);
-    $this->assertEquals($items[2]['mlid'], $tree['50000  2']['link']['mlid']);
-    $this->assertEquals($items[5]['mlid'], $tree['50000  5']['link']['mlid']);
-
-    // Validate that child item #4 exists at the correct location in the hierarchy.
-    $this->assertEquals($items[4]['mlid'], $tree['50000  2']['below']['50000  3']['below']['50000  4']['link']['mlid']);
-  }
-
-  /**
-   * Tests getActiveTrailIds().
-   *
-   * @covers ::getActiveTrailIds()
-   */
-  public function testGetActiveTrailIds() {
-    $menu_link = array(
-      'mlid' => 10,
-      'route_name' => 'example1',
-      'p1' => 3,
-      'p2' => 2,
-      'p3' => 1,
-      'p4' => 4,
-      'p5' => 9,
-      'p6' => 5,
-      'p7' => 6,
-      'p8' => 7,
-      'p9' => 8,
-      'menu_name' => 'test_menu'
-    );
-    $this->menuTree->setPreferredMenuLink('test_menu', 'test/path', $menu_link);
-    $request = (new Request());
-    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route');
-    $this->requestStack->push($request);
-    $this->menuTree->setPath('test_menu', 'test/path');
-
-    $trail = $this->menuTree->getActiveTrailIds('test_menu');
-    $this->assertEquals(array(0 => 0, 3 => 3, 2 => 2, 1 => 1, 4 => 4, 9 => 9, 5 => 5, 6 => 6, 7 => 7), $trail);
-  }
-
-  /**
-   * Tests getActiveTrailIds() without preferred link.
-   *
-   * @covers ::getActiveTrailIds()
-   */
-  public function testGetActiveTrailIdsWithoutPreferredLink() {
-    $request = (new Request());
-    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route');
-    $this->requestStack->push($request);
-    $this->menuTree->setPath('test_menu', 'test/path');
-
-    $trail = $this->menuTree->getActiveTrailIds('test_menu');
-    $this->assertEquals(array(0 => 0), $trail);
-  }
-
-
-  /**
-   * Tests buildTree with simple menu_name and no parameters.
-   */
-  public function testBuildTreeWithoutParameters() {
-    $language = new Language(array('id' => 'en'));
-    $this->languageManager->expects($this->any())
-      ->method('getCurrentLanguage')
-      ->will($this->returnValue($language));
-
-    // Setup query and the query result.
-    $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
-    $this->entityQueryFactory->expects($this->once())
-      ->method('get')
-      ->with('menu_link')
-      ->will($this->returnValue($query));
-    $query->expects($this->once())
-      ->method('condition')
-      ->with('menu_name', 'test_menu');
-    $query->expects($this->once())
-      ->method('execute')
-      ->will($this->returnValue(array(1, 2, 3)));
-
-    $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
-    $base = array(
-      'access' => TRUE,
-      'weight' => 0,
-      'title' => 'title',
-    );
-    $menu_link = $base + array(
-      'mlid' => 1,
-      'p1' => 3,
-      'p2' => 2,
-      'p3' => 1,
-    );
-    $links[1] = $menu_link;
-    $menu_link = $base + array(
-      'mlid' => 3,
-      'p1' => 3,
-      'depth' => 1,
-    );
-    $links[3] = $menu_link;
-    $menu_link = $base + array(
-      'mlid' => 2,
-      'p1' => 3,
-      'p2' => 2,
-      'depth' => 2,
-    );
-    $links[2] = $menu_link;
-    $storage->expects($this->once())
-      ->method('loadMultiple')
-      ->with(array(1, 2, 3))
-      ->will($this->returnValue($links));
-    $this->menuTree->setStorage($storage);
-
-    // Ensure that static/non static caching works.
-    // First setup no working caching.
-    $this->cacheBackend->expects($this->at(0))
-      ->method('get')
-      ->with('links:test_menu:tree-data:en:35786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3')
-      ->will($this->returnValue(FALSE));
-    $this->cacheBackend->expects($this->at(1))
-      ->method('set')
-      ->with('links:test_menu:tree-data:en:35786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3', $this->anything(), Cache::PERMANENT, array('menu' => 'test_menu'));
-
-    // Ensure that the static caching triggered.
-    $this->cacheBackend->expects($this->exactly(1))
-      ->method('get');
-
-    $this->menuTree->buildTree('test_menu');
-    $this->menuTree->buildTree('test_menu');
-  }
-
-  /**
-   * Tests the output with a single level.
-   *
-   * @covers ::renderTree
-   */
-  public function testOutputWithSingleLevel() {
-    $tree = array(
-      '1' => array(
-        'link' => array('mlid' => 1) + $this->defaultMenuLink,
-        'below' => array(),
-      ),
-      '2' => array(
-        'link' => array('mlid' => 2) + $this->defaultMenuLink,
-        'below' => array(),
-      ),
-    );
-
-    $output = $this->menuTree->renderTree($tree);
-
-    // Validate that the - in main-menu is changed into an underscore
-    $this->assertEquals($output['1']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link');
-    $this->assertEquals($output['2']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link');
-    $this->assertEquals($output['#theme_wrappers'][0], 'menu_tree__main_menu', 'Hyphen is changed to an underscore on menu_tree wrapper');
-  }
-
-  /**
-   * Tests the output method with a complex example.
-   *
-   * @covers ::renderTree
-   */
-  public function testOutputWithComplexData() {
-    $tree = array(
-      '1'=> array(
-        'link' => array('mlid' => 1, 'has_children' => 1, 'title' => 'Item 1', 'link_path' => 'a') + $this->defaultMenuLink,
-        'below' => array(
-          '2' => array('link' => array('mlid' => 2, 'title' => 'Item 2', 'link_path' => 'a/b') + $this->defaultMenuLink,
-            'below' => array(
-              '3' => array('link' => array('mlid' => 3, 'title' => 'Item 3', 'in_active_trail' => 0, 'link_path' => 'a/b/c') + $this->defaultMenuLink,
-                'below' => array()),
-              '4' => array('link' => array('mlid' => 4, 'title' => 'Item 4', 'in_active_trail' => 0, 'link_path' => 'a/b/d') + $this->defaultMenuLink,
-                'below' => array())
-            )
-          )
-        )
-      ),
-      '5' => array('link' => array('mlid' => 5, 'hidden' => 1, 'title' => 'Item 5', 'link_path' => 'e') + $this->defaultMenuLink, 'below' => array()),
-      '6' => array('link' => array('mlid' => 6, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'link_path' => 'f') + $this->defaultMenuLink, 'below' => array()),
-      '7' => array('link' => array('mlid' => 7, 'title' => 'Item 7', 'in_active_trail' => 0, 'link_path' => 'g') + $this->defaultMenuLink, 'below' => array())
-    );
-
-    $output = $this->menuTree->renderTree($tree);
-
-    // Looking for child items in the data
-    $this->assertEquals( $output['1']['#below']['2']['#href'], 'a/b', 'Checking the href on a child item');
-    $this->assertTrue(in_array('active-trail', $output['1']['#below']['2']['#attributes']['class']), 'Checking the active trail class');
-    // Validate that the hidden and no access items are missing
-    $this->assertFalse(isset($output['5']), 'Hidden item should be missing');
-    $this->assertFalse(isset($output['6']), 'False access should be missing');
-    // Item 7 is after a couple hidden items. Just to make sure that 5 and 6 are
-    // skipped and 7 still included.
-    $this->assertTrue(isset($output['7']), 'Item after hidden items is present');
-  }
-
-  /**
-   * Tests menu tree access check with a single level.
-   *
-   * @covers ::checkAccess
-   */
-  public function testCheckAccessWithSingleLevel() {
-    $items = array(
-      array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1', 'in_active_trail' => FALSE) + $this->defaultMenuLink,
-      array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2', 'in_active_trail' => FALSE) + $this->defaultMenuLink,
-    );
-
-    // Register a menuLinkTranslate to mock the access.
-    $this->menuTree->menuLinkTranslateCallable = function(&$item) {
-      $item['access'] = $item['mlid'] == 1;
-    };
-
-    // Build the menu tree and check access for all of the items.
-    $tree = $this->menuTree->buildTreeData($items);
-
-    $this->assertCount(1, $tree);
-    $item = reset($tree);
-    $this->assertEquals($items[0], $item['link']);
-  }
-
-}
-
-class TestMenuTree extends MenuTree {
-
-  /**
-   * An alternative callable used for menuLinkTranslate.
-   * @var callable
-   */
-  public $menuLinkTranslateCallable;
-
-  /**
-   * Stores the preferred menu link per menu and path.
-   *
-   * @var array
-   */
-  protected $preferredMenuLink;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function menuLinkTranslate(&$item) {
-    if (isset($this->menuLinkTranslateCallable)) {
-      call_user_func_array($this->menuLinkTranslateCallable, array(&$item));
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function menuLinkGetPreferred($menu_name, $active_path) {
-    return isset($this->preferredMenuLink[$menu_name][$active_path]) ? $this->preferredMenuLink[$menu_name][$active_path] : NULL;
-  }
-
-  /**
-   * Set the storage.
-   *
-   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
-   *   The menu link storage.
-   */
-  public function setStorage(EntityStorageInterface $storage) {
-    $this->menuLinkStorage = $storage;
-  }
-
-  /**
-   * Sets the preferred menu link.
-   *
-   * @param string $menu_name
-   *   The menu name.
-   * @param string $active_path
-   *   The active path.
-   * @param array $menu_link
-   *   The preferred menu link.
-   */
-  public function setPreferredMenuLink($menu_name, $active_path, $menu_link) {
-    $this->preferredMenuLink[$menu_name][$active_path] = $menu_link;
-  }
-
-}
-
-}
-
-namespace {
-  if (!defined('MENU_MAX_DEPTH')) {
-    define('MENU_MAX_DEPTH', 9);
-  }
-}
diff --git a/core/modules/menu_link_content/lib/Drupal/menu_link_content/Controller/MenuController.php b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Controller/MenuController.php
new file mode 100644
index 0000000..5f93db8
--- /dev/null
+++ b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Controller/MenuController.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Controller\MenuController.
+ */
+
+namespace Drupal\menu_link_content\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\system\MenuInterface;
+
+class MenuController extends ControllerBase {
+
+  /**
+   * 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_content')->create(array(
+      'id' => '',
+      'parent' => '',
+      'menu_name' => $menu->id(),
+      'bundle' => 'menu_link_content',
+    ));
+    return $this->entityFormBuilder()->getForm($menu_link);
+  }
+
+}
+
diff --git a/core/modules/menu_link_content/lib/Drupal/menu_link_content/Entity/MenuLinkContent.php b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Entity/MenuLinkContent.php
new file mode 100644
index 0000000..46e1511
--- /dev/null
+++ b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Entity/MenuLinkContent.php
@@ -0,0 +1,391 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Entity\MenuLinkContent.
+ */
+
+namespace Drupal\menu_link_content\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\FieldDefinition;
+use Drupal\Core\Url;
+
+/**
+ * Defines the menu link content entity class.
+ *
+ * @EntityType(
+ *   id = "menu_link_content",
+ *   label = @Translation("Menu link content"),
+ *   controllers = {
+ *     "storage" = "Drupal\Core\Entity\ContentEntityDatabaseStorage",
+ *     "form" = {
+ *       "default" = "Drupal\menu_link_content\Form\MenuLinkContentForm",
+ *       "delete" = "Drupal\menu_link_content\Form\MenuLinkContentDeleteForm"
+ *     }
+ *   },
+ *   admin_permission = "administer menu",
+ *   static_cache = FALSE,
+ *   base_table = "menu_link_content",
+ *   data_table = "menu_link_content_data",
+ *   fieldable = TRUE,
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "title",
+ *     "uuid" = "uuid",
+ *     "bundle" = "bundle"
+ *   },
+ * )
+ */
+class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->get('title')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return $this->get('route_name')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteParameters() {
+    // @TODO WTF. there need to be an easier way.
+    if ($values = $this->get('route_parameters')->first()->value) {
+      return $values;
+    }
+    else {
+      $values = $this->get('route_parameters')->first()->getValues();
+      unset($values['value']);
+      return $values;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRouteParameters(array $route_parameters) {
+    $this->set('route_parameters', array('value' => $route_parameters));
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUrl() {
+    return $this->get('url')->value ?: NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUrlObject() {
+    if ($route_name = $this->getRouteName()) {
+      $url = new Url($route_name, $this->getRouteParameters(), $this->getOptions());
+    }
+    else {
+      $path = $this->getUrl();
+      if (isset($path)) {
+        $url = Url::createFromPath($path);
+      }
+      else {
+        $url = new Url('<front>');
+      }
+    }
+
+    return $url;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMenuName() {
+    return $this->get('menu_name')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptions() {
+    // @TODO WTF. there need to be an easier way.
+    if ($values = $this->get('options')->first()->value) {
+      return $values;
+    }
+    else {
+      $values = $this->get('options')->first()->getValues();
+      unset($values['value']);
+      return $values;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOptions(array $options) {
+    $this->set('options', array('value' => $options));
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->get('description')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginId() {
+    return 'menu_link_content:' . $this->uuid();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isHidden() {
+    return (bool) $this->get('hidden')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExpanded() {
+    return (bool) $this->get('expanded')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParentId() {
+    return $this->get('parent')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return (int) $this->get('weight')->value;
+  }
+
+  /**
+   * Builds up the menu plugin definition for this entity.
+   *
+   * @return array
+   *
+   * @see \Drupal\Core\Menu\MenuLinkTree::$defaults
+   */
+  protected function getMenuDefinition() {
+    $definition = array();
+    $definition['class'] = 'Drupal\menu_link_content\Plugin\Menu\MenuLinkContent';
+    $definition['menu_name'] = $this->getMenuName();
+    $definition['route_name'] = $this->getRouteName();
+    $definition['route_parameters'] = $this->getRouteParameters();
+    $definition['url'] = $this->getUrl();
+    $definition['options'] = $this->getOptions();
+    // Don't bother saving title and description strings, since they are never
+    // used.
+    $definition['title'] = '';
+    $definition['description'] = '';
+    $definition['weight'] = $this->getWeight();
+    $definition['id'] = $this->getPluginId();
+    $definition['metadata'] = array('entity_id' => $this->id());
+    $definition['form_class'] = '\Drupal\menu_link_content\Form\MenuLinkContentForm';
+    $definition['hidden'] = $this->isHidden() ? 1 : 0;
+    $definition['expanded'] = $this->isExpanded() ? 1 : 0;
+    $definition['provider'] = 'menu_link_content';
+    $definition['discovered'] = 0;
+    $definition['parent'] = $this->getParentId();
+
+    return $definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageInterface $storage) {
+    parent::preSave($storage);
+
+    // Find the route_name.
+    if ($input_url = $this->getUrl()) {
+      $url = Url::createFromPath($this->getUrl());
+      if (!$url->isExternal()) {
+        $this->route_name->value = $url->getRouteName();
+        $this->setRouteParameters($url->getRouteParameters());
+        $this->url->value = '';
+      }
+      else {
+        // We don't store the route name/parameters for external urls.
+        $this->route_name->value = NULL;
+        $this->setRouteParameters(array());
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+    parent::postSave($storage, $update);
+
+    /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+    $menu_tree = \Drupal::menuTree();
+
+    if (!$update) {
+      $menu_tree->createLink($this->getPluginId(), $this->getMenuDefinition());
+    }
+    else {
+      $menu_tree->updateLink($this->getPluginId(), $this->getMenuDefinition(), FALSE);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preDelete(EntityStorageInterface $storage, array $entities) {
+    parent::preDelete($storage, $entities);
+
+    foreach ($entities as $menu_link) {
+      /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $menu_link */
+      \Drupal::menuTree()->deleteLink($menu_link->getPluginId(), FALSE);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields['id'] = FieldDefinition::create('integer')
+      ->setLabel(t('Content menu link ID'))
+      ->setDescription(t('The menu link ID.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+    $fields['uuid'] = FieldDefinition::create('uuid')
+      ->setLabel(t('UUID'))
+      ->setDescription(t('The content menu link UUID.'))
+      ->setReadOnly(TRUE);
+
+    $fields['bundle'] = FieldDefinition::create('string')
+      ->setLabel(t('Bundle'))
+      ->setDescription(t('The content menu link bundle.'))
+      ->setReadOnly(TRUE);
+
+    $fields['title'] = FieldDefinition::create('string')
+      ->setLabel(t('Menu link title'))
+      ->setDescription(t('The text to be used for this link in the menu.'))
+      ->setRequired(TRUE)
+      ->setTranslatable(TRUE)
+      ->setSettings(array(
+        'default_value' => '',
+        'max_length' => 255,
+      ))
+      ->setDisplayOptions('view', array(
+        'label' => 'hidden',
+        'type' => 'string',
+        'weight' => -5,
+      ))
+      ->setDisplayOptions('form', array(
+        'type' => 'string',
+        'weight' => -5,
+      ))
+      ->setDisplayConfigurable('form', TRUE);
+
+    $fields['description'] = FieldDefinition::create('string')
+      ->setLabel(t('Description'))
+      ->setDescription(t('Shown when hovering over the menu link.'))
+      ->setTranslatable(TRUE)
+      ->setDisplayOptions('view', array(
+        'label' => 'hidden',
+        'type' => 'string',
+        'weight' => 0,
+      ))
+      ->setDisplayOptions('form', array(
+        'type' => 'string',
+        'weight' => 0,
+      ));
+
+    $fields['menu_name'] = FieldDefinition::create('string')
+      ->setLabel(t('Menu name'))
+      ->setDescription(t('The menu name. All links with the same menu name (such as "tools") are part of the same menu.'));
+      //      ->setSetting('default_value', 'tools')
+
+    // @todo use a link field in the end? see https://drupal.org/node/2235457
+    $fields['route_name'] = FieldDefinition::create('string')
+      ->setLabel(t('Route name'))
+      ->setDescription(t('The machine name of a defined Symfony Route this menu item represents.'));
+
+    $fields['route_parameters'] = FieldDefinition::create('map')
+      ->setLabel(t('Route parameters'))
+      ->setDescription(t('A serialized array of route parameters of this menu link.'));
+
+    $fields['url'] = FieldDefinition::create('string')
+      ->setLabel(t('External link url'))
+      ->setDescription(t('The url of the link, in case you have an external link.'));
+
+    $fields['options'] = FieldDefinition::create('map')
+      ->setLabel(t('Options'))
+      ->setDescription(t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'))
+      ->setSetting('default_value', array());
+
+    $fields['external'] = FieldDefinition::create('boolean')
+      ->setLabel(t('External'))
+      ->setDescription(t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'))
+      ->setSetting('default_value', 0);
+
+    $fields['expanded'] = FieldDefinition::create('boolean')
+      ->setLabel(t('Expanded'))
+      ->setDescription(t('Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded).'))
+      ->setSetting('default_value', 0)
+      ->setDisplayOptions('view', array(
+        'label' => 'hidden',
+        'type' => 'boolean',
+        'weight' => 0,
+      ))
+      ->setDisplayOptions('form', array(
+        'type' => 'options_onoff',
+        'weight' => 0,
+      ));
+
+    $fields['hidden'] = FieldDefinition::create('boolean')
+      ->setLabel(t('Hidden'))
+      ->setDescription(t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link).'))
+      ->setSetting('default_value', 0);
+
+    $fields['weight'] = FieldDefinition::create('integer')
+      ->setLabel(t('Weight'))
+      ->setDescription(t('Link weight among links in the same menu at the same depth.'))
+      ->setSetting('default_value', 0)
+      ->setDisplayOptions('view', array(
+        'label' => 'hidden',
+        'type' => 'integer',
+        'weight' => 0,
+      ))
+      ->setDisplayOptions('form', array(
+        'type' => 'integer',
+        'weight' => 0,
+      ));
+
+    $fields['langcode'] = FieldDefinition::create('language')
+      ->setLabel(t('Language code'))
+      ->setDescription(t('The node language code.'));
+
+    $fields['parent'] = FieldDefinition::create('string')
+      ->setLabel(t('Parent menu link ID'))
+      ->setDescription(t('The parent menu link ID.'));
+
+    return $fields;
+  }
+
+}
+
diff --git a/core/modules/menu_link_content/lib/Drupal/menu_link_content/Entity/MenuLinkContentInterface.php b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Entity/MenuLinkContentInterface.php
new file mode 100644
index 0000000..5aa23b2
--- /dev/null
+++ b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Entity/MenuLinkContentInterface.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Entity\MenuLinkContentInterface.
+ */
+
+namespace Drupal\menu_link_content\Entity;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+
+/**
+ * Defines an interface for custom menu links.
+ */
+interface MenuLinkContentInterface extends ContentEntityInterface {
+
+  /**
+   * Gets the title of the menu link.
+   *
+   * @return mixed
+   */
+  public function getTitle();
+
+  /**
+   * Gets the route name of the custom menu link.
+   *
+   * @return string|NULL
+   *   Returns the route name, unless it is an internal link.
+   */
+  public function getRouteName();
+
+  /**
+   * Gets the route parameters of the custom menu link.
+   *
+   * @return array
+   */
+  public function getRouteParameters();
+
+  /**
+   * Sets the route paramters of the custom menu link.
+   *
+   * @param array $route_parameters
+   *   The route parameters
+   *
+   * @return $this
+   */
+  public function setRouteParameters(array $route_parameters);
+
+  /**
+   * Gets the external URL.
+   *
+   * @return string|NULL
+   *   Returns the external URL if the menu link points to an external URL,
+   *   otherwise NULL.
+   */
+  public function getUrl();
+
+  /**
+   * Gets the url object pointing to the URL of the custom menu link.
+   *
+   * @return \Drupal\Core\Url
+   */
+  public function getUrlObject();
+
+  /**
+   * Gets the menu name of the custom menu link.
+   *
+   * @return string
+   *   The menu ID.
+   */
+  public function getMenuName();
+
+  /**
+   * Gets the query options of the custom menu link.
+   *
+   * @return array
+   */
+  public function getOptions();
+
+  public function setOptions(array $options);
+
+  /**
+   * Gets the description of the custom menu link for the UI.
+   *
+   * @return string
+   */
+  public function getDescription();
+
+  /**
+   * Gets the menu plugin ID associated with this entity.
+   *
+   * @return string
+   */
+  public function getPluginId();
+
+  /**
+   * Returns whether the menu link is marked as hidden.
+   *
+   * @return bool
+   *   TRUE if is not enabled, otherwise FALSE.
+   */
+  public function isHidden();
+
+  /**
+   * Returns whether the menu link is marked as always expanded.
+   *
+   * @return bool
+   */
+  public function isExpanded();
+
+  /**
+   * Gets the menu plugin ID of the parent menu link.
+   *
+   * @return string
+   */
+  public function getParentId();
+
+  /**
+   * Returns the weight of the custom menu link.
+   *
+   * @return int
+   */
+  public function getWeight();
+
+}
diff --git a/core/modules/menu_link_content/lib/Drupal/menu_link_content/Form/MenuLinkContentDeleteForm.php b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Form/MenuLinkContentDeleteForm.php
new file mode 100644
index 0000000..97a4423
--- /dev/null
+++ b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Form/MenuLinkContentDeleteForm.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Form\MenuLinkContentDeleteForm.
+ */
+
+namespace Drupal\menu_link_content\Form;
+
+use Drupal\Core\Entity\ContentEntityConfirmFormBase;
+
+/**
+ * Provides a delete form for content menu links.
+ */
+class MenuLinkContentDeleteForm extends ContentEntityConfirmFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete the custom menu link %item?', array('%item' => $this->entity->getTitle()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelRoute() {
+    return array(
+      'route_name' => 'menu_ui.menu_edit',
+      'route_parameters' => array(
+        'menu' => $this->entity->getMenuName(),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submit(array $form, array &$form_state) {
+    $storage = $this->entityManager->getStorage('menu_link_content');
+    $storage->delete(array($this->entity));
+    $t_args = array('%title' => $this->entity->getTitle());
+    drupal_set_message($this->t('The menu link %title has been deleted.', $t_args));
+    watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE);
+    $form_state['redirect_route'] = array(
+      'route_name' => 'menu_ui.menu_edit',
+      'route_parameters' => array(
+        'menu' => $this->entity->getMenuName(),
+      ),
+    );
+  }
+
+}
diff --git a/core/modules/menu_link_content/lib/Drupal/menu_link_content/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Form/MenuLinkContentForm.php
new file mode 100644
index 0000000..3efd0ae
--- /dev/null
+++ b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Form/MenuLinkContentForm.php
@@ -0,0 +1,328 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Form\MenuLinkContentForm.
+ */
+
+namespace Drupal\menu_link_content\Form;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Menu\Form\MenuLinkFormInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Path\AliasManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Routing\RequestContext;
+
+/**
+ * Provides a form to add/update content menu links.
+ *
+ * Note: This is not only a content entity form, but also implements the
+ * MenuLinkFormInterface, which works as a generic menu link edit form, so for
+ * example static menu links as well.
+ */
+class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInterface {
+
+  /**
+   * The content menu link.
+   *
+   * @var \Drupal\menu_link_content\Entity\MenuLinkContent
+   */
+  protected $entity;
+
+  /**
+   * The menu tree.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuTree;
+
+  /**
+   * The request context.
+   *
+   * @var \Symfony\Component\Routing\RequestContext
+   */
+  protected $requestContext;
+
+  /**
+   * Constructs a MenuLinkContentForm object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   The menu tree.
+   * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
+   *   The alias manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler;
+   * @param \Symfony\Component\Routing\RequestContext $request_context
+   *   The request context.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, MenuLinkTreeInterface $menu_tree, AliasManagerInterface $alias_manager, ModuleHandlerInterface $module_handler, RequestContext $request_context) {
+    parent::__construct($entity_manager);
+    $this->menuTree = $menu_tree;
+    $this->pathAliasManager = $alias_manager;
+    $this->moduleHandler = $module_handler;
+    $this->requestContext = $request_context;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('menu.link_tree'),
+      $container->get('path.alias_manager'),
+      $container->get('module_handler'),
+      $container->get('router.request_context')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link) {
+    // Load the entity for the entity form using the UUID of the custom menu
+    // link plugin ID.
+    $links = $this->entityManager->getStorage('menu_link_content')->loadByProperties(array('uuid' => $menu_link->getDerivativeId()));
+    $this->entity = reset($links);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildEditForm(array &$form, array &$form_state) {
+    $this->setOperation('default');
+    $this->init($form_state);
+
+    return $this->form($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateEditForm(array &$form, array &$form_state) {
+    $this->doValidate($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitEditForm(array &$form, array &$form_state) {
+    $new_definition = $this->extractFormValues($form, $form_state);
+
+    return $this->menuTree->updateLink($this->entity->getPluginId(), $new_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFormValues(array &$form, array &$form_state) {
+    $this->entity = $this->buildEntity($form, $form_state);
+    $this->entity->save();
+
+    $new_definition = array();
+    $new_definition['expanded'] = $this->entity->isExpanded();
+    $new_definition['hidden'] = $this->entity->isHidden();
+    $new_definition['route_name'] = $this->entity->getRouteName();
+    $new_definition['route_parameters'] = $this->entity->getRouteParameters();
+    $new_definition['weight'] = $this->entity->getWeight();
+    list($menu_name, $parent) = explode(':', $form_state['values']['menu_parent'], 2);
+    if (!empty($menu_name)) {
+      $new_definition['menu_name'] = $menu_name;
+    }
+    if (isset($parent)) {
+      $new_definition['parent'] = $parent;
+    }
+    $new_definition['description'] = $this->entity->getDescription();
+    $new_definition['options'] = $this->entity->getOptions();
+    return $new_definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, array &$form_state) {
+    $form = parent::form($form, $form_state);
+
+    $form['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enable menu link'),
+//      '#title_display' => 'invisible',
+      '#description' => $this->t('Menu links that are not enabled will not be listed in any menu.'),
+      '#default_value' => !$this->entity->isHidden(),
+    );
+
+    // @TODO For whatever reason the expanded widget is not autogenerated.
+    $form['expanded'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Show as expanded'),
+      '#description' => $this->t('If selected and this menu link has children, the menu will always appear expanded.'),
+      '#default_value' => $this->entity->isExpanded(),
+    );
+
+    // @todo Should we show the internal path of the path alias here?
+    $url = $this->getEntity()->getUrlObject();
+    $default_value = $url->toString();
+    if (!$url->isExternal()) {
+      // @TODO Maybe support options in
+      // \Drupal\Core\Routing\UrlGeneratorInterface::getInternalPath().
+      $base_url = $this->requestContext->getBaseUrl();
+      $default_value = substr($default_value, strlen($base_url) + 1);
+    }
+    $form['url'] = array(
+      '#title' => $this->t('Link path'),
+      '#type' => 'textfield',
+      '#description' => $this->t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
+      '#default_value' => $default_value,
+      '#required' => TRUE,
+    );
+
+    $options = $this->menuTree->getParentSelectOptions($this->entity->getPluginId());
+    $menu_parent =  $this->entity->getMenuName() . ':' . $this->entity->getParentId();
+
+    if (!isset($options[$menu_parent])) {
+      // Put it at the top level in the current menu.
+      $menu_parent = $this->entity->getMenuName() . ':';
+    }
+    $form['menu_parent'] = array(
+      '#type' => 'select',
+      '#title' => $this->t('Parent link'),
+      '#options' => $options,
+      '#default_value' => $menu_parent,
+      '#description' => $this->t('The maximum depth for a link and all its children is fixed at !maxdepth. Some menu links may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => $this->menuTree->maxDepth())),
+      '#attributes' => array('class' => array('menu-title-select')),
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, array &$form_state) {
+    $element = parent::actions($form, $form_state);
+    $element['submit']['#button_type'] = 'primary';
+    $element['delete']['#access'] = $this->entity->access('delete');
+
+    return $element;
+  }
+
+  public function validate(array $form, array &$form_state) {
+    $this->doValidate($form, $form_state);
+
+    parent::validate($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildEntity(array $form, array &$form_state) {
+    /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $entity */
+    $entity = parent::buildEntity($form, $form_state);
+
+    // @TODO Is there a better way to define default value?
+    $options = $entity->options->value;
+    if (!isset($options)) {
+      $entity->options->value = array();
+    }
+
+    $entity->hidden->value = !$form_state['values']['enabled'];
+
+    list($menu_name, $parent) = explode(':', $form_state['values']['menu_parent'], 2);
+    if (!empty($menu_name)) {
+      $entity->menu_name->value =$menu_name;
+    }
+    if (isset($parent)) {
+      $entity->parent->value = $parent;
+    }
+
+    $entity->expanded->value = (bool) $form_state['values']['expanded'];
+
+    $entity->url->value = $form_state['values']['url'];
+
+    if (!UrlHelper::isExternal($entity->getUrl())) {
+      $parsed_link = parse_url($entity->getUrl());
+      if (isset($parsed_link['query'])) {
+        $query = array();
+        parse_str($parsed_link['query'], $query);
+
+        $options = $entity->getOptions();
+        $options['query'] = $query;
+        $entity->setOptions($options);
+      }
+      else {
+        // Use unset() rather than setting to empty string
+        // to avoid redundant serialized data being stored.
+        $options = $entity->getOptions();
+        unset($options['query']);
+        $entity->setOptions($options);
+      }
+      if (isset($parsed_link['fragment'])) {
+        $options = $entity->getOptions();
+        $options['fragment'] = $parsed_link['fragment'];
+        $entity->setOptions($options);
+      }
+      else {
+        $options = $entity->getOptions();
+        unset($options['fragment']);
+        $entity->setOptions($options);
+      }
+      if (isset($parsed_link['path']) && $entity->getUrl() != $parsed_link['path']) {
+        $entity->url->value = $parsed_link['path'];
+      }
+    }
+
+    return $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, array &$form_state) {
+    $menu_link = $this->entity;
+    $saved = $menu_link->save();
+
+    if ($saved) {
+      drupal_set_message(t('The menu link has been saved.'));
+      $form_state['redirect_route'] = array(
+        'route_name' => 'menu_ui.menu_edit',
+        'route_parameters' => array(
+          'menu' => $menu_link->getMenuName(),
+        ),
+      );
+    }
+    else {
+      drupal_set_message(t('There was an error saving the menu link.'), 'error');
+      $form_state['rebuild'] = TRUE;
+    }
+  }
+
+  /**
+   * Validates the form, both on the menu link edit and content menu link form.
+   */
+  protected function doValidate(array $form, array &$form_state) {
+    $menu_link = $this->buildEntity($form, $form_state);
+
+    $normal_path = $this->pathAliasManager->getPathByAlias($menu_link->getUrl());
+
+    // @todo Can we leverage constrains here?
+    if ($menu_link->getUrl() != $normal_path) {
+      drupal_set_message($this->t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array(
+            '%link_path' => $menu_link->getUrl(),
+            '%normal_path' => $normal_path
+          )));
+      $menu_link->url->value = $normal_path;
+      $form_state['values']['url'] = $normal_path;
+    }
+    if (!trim($menu_link->getUrl()) || !drupal_valid_path($menu_link->getUrl(), TRUE)) {
+      $this->setFormError('url', $form_state, $this->t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->getUrl())));
+    }
+  }
+
+}
diff --git a/core/modules/menu_link_content/lib/Drupal/menu_link_content/Plugin/Menu/MenuLinkContent.php b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Plugin/Menu/MenuLinkContent.php
new file mode 100644
index 0000000..4617e76
--- /dev/null
+++ b/core/modules/menu_link_content/lib/Drupal/menu_link_content/Plugin/Menu/MenuLinkContent.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent.
+ */
+
+namespace Drupal\menu_link_content\Plugin\Menu;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Menu\MenuLinkBase;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides the menu link plugin for content menu link.s
+ */
+class MenuLinkContent extends MenuLinkBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Defines all overrideable values.
+   *
+   * @var array
+   */
+  protected $overrideAllowed = array(
+    'menu_name' => 1,
+    'parent' => 1,
+    'weight' => 1,
+    'expanded' => 1,
+    'hidden' => 1,
+    'title' => 1,
+    'description' => 1,
+    'route_name' => 1,
+    'route_parameters' => 1,
+    'url' => 1,
+    'options' => 1,
+  );
+
+  /**
+   * The menu link content entity connected to this plugin instance.
+   *
+   * @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface
+   */
+  protected $entity;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new MenuLinkContent.
+   *
+   * @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 static override storage.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * {@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')
+    );
+  }
+
+  /**
+   * Loads the entity associated with this menu link.
+   *
+   * @return \Drupal\menu_link_content\Entity\MenuLinkContentInterface
+   */
+  protected function getEntity() {
+    if (empty($this->entity)) {
+      $storage = $this->entityManager->getStorage('menu_link_content');
+      if (!empty($this->pluginDefinition['metadata']['entity_id'])) {
+        $this->entity = $storage->load($this->pluginDefinition['metadata']['entity_id']);
+      }
+      else {
+        // Fallback to the loading by the uuid.
+        $uuid = $this->getDerivativeId();
+        $links = $storage->loadByProperties(array('uuid' => $uuid));
+        $this->entity = reset($links);
+      }
+    }
+    return $this->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->getEntity()->getTitle();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->getEntity()->getDescription();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDeleteRoute() {
+    return array(
+      'route_name' => 'menu_link_content.link_delete',
+      'route_parameters' => array('menu_link_content' => $this->getEntity()->id()),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateLink(array $new_definition_values, $persist) {
+    $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
+    // Update the definition.
+    $this->pluginDefinition = $overrides + $this->getPluginDefinition();
+    if ($persist) {
+      $entity = $this->getEntity();
+      foreach ($overrides as $key => $value) {
+        $entity->{$key}->value = $value;
+      }
+      $this->entityManager->getStorage('menu_link_content')->save($entity);
+    }
+
+    return $this->pluginDefinition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isDeletable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteLink() {
+    // @todo: Flag this call if possible so we don't call the menu tree manager.
+    $this->getEntity()->delete();
+  }
+
+}
diff --git a/core/modules/menu_link_content/menu_link_content.info.yml b/core/modules/menu_link_content/menu_link_content.info.yml
new file mode 100644
index 0000000..4c69873
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.info.yml
@@ -0,0 +1,6 @@
+name: 'Menu Links Content'
+type: module
+description: 'Allows administrators to create custom links'
+package: Core
+version: VERSION
+core: 8.x
diff --git a/core/modules/menu_link_content/menu_link_content.install b/core/modules/menu_link_content/menu_link_content.install
new file mode 100644
index 0000000..e8420a4
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.install
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the menu_link_content module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function menu_link_content_schema() {
+  $schema['menu_link_content'] = array(
+    'description' => 'Contains the individual links within a menu.',
+    'fields' => array(
+      'id' => array(
+        'description' => 'The menu link ID.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'uuid' => array(
+        'description' => 'Unique Key: Universally unique identifier for this entity.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+      ),
+      'bundle' => array(
+        'description' => 'The bundle',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('id'),
+  );
+
+  $schema['menu_link_content_data'] = array(
+    'description' => 'Contains the individual links within a menu.',
+    'fields' => array(
+     'menu_name' => array(
+        'description' => "The menu name. All links with the same menu name (such as 'tools') are part of the same menu.",
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'id' => array(
+        'description' => 'The menu link ID.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'parent' => array(
+        'description' => 'The parent menu link ID.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+      ),
+      'url' => array(
+        'description' => 'The Drupal path or external path this link points to.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of these menu link property values.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'default_langcode' => array(
+        'description' => 'Boolean indicating whether the property values are in the {language}.langcode of this menu link.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+      'title' => array(
+        'description' => 'The text displayed for the link.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'options' => array(
+        'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
+        'type' => 'blob',
+        'not null' => FALSE,
+        'serialize' => TRUE,
+      ),
+      'hidden' => array(
+        'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu link that may be shown on admin screens, 0 = a normal, visible link)',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'expanded' => array(
+        'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'weight' => array(
+        'description' => 'Link weight among links in the same menu at the same depth.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'route_name' => array(
+        'description' => 'The machine name of a defined Symfony Route this menu item represents.',
+        'type' => 'varchar',
+        'length' => 255,
+      ),
+      'route_parameters' => array(
+        'description' => 'Serialized array of route parameters of this menu link.',
+        'type' => 'blob',
+        'size' => 'big',
+        'not null' => FALSE,
+        'serialize' => TRUE,
+      ),
+    ),
+    'primary key' => array('id', 'langcode'),
+    'indexes' => array(
+      'menu_name' => array('menu_name'),
+    ),
+  );
+
+  return $schema;
+}
diff --git a/core/modules/menu_link_content/menu_link_content.routing.yml b/core/modules/menu_link_content/menu_link_content.routing.yml
new file mode 100644
index 0000000..b5f068c
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.routing.yml
@@ -0,0 +1,15 @@
+menu_link_content.link_add:
+  path: '/admin/structure/menu/manage/{menu}/add'
+  defaults:
+    _content: '\Drupal\menu_link_content\Controller\MenuController::addLink'
+    _title: 'Add menu link'
+  requirements:
+    _entity_create_access: 'menu_link_content'
+
+menu_link_content.link_delete:
+  path: '/admin/structure/menu/item/{menu_link_content}/delete'
+  defaults:
+    _entity_form: 'menu_link_content.delete'
+    _title: 'Delete menu link'
+  requirements:
+    _entity_access: 'menu_link_content.delete'
diff --git a/core/modules/menu_ui/lib/Drupal/menu_ui/Controller/MenuController.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Controller/MenuController.php
index fad5de5..02c59d0 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Controller/MenuController.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/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\MenuLinkTreeInterface $menu_tree */
+    $menu_tree = \Drupal::service('menu.link_tree');
+    $options = $menu_tree->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/lib/Drupal/menu_ui/Form/MenuDeleteForm.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuDeleteForm.php
index 98a45db..5e27df5 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuDeleteForm.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/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\MenuLinkTreeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -22,7 +22,7 @@ class MenuDeleteForm extends EntityConfirmFormBase {
    *
    * @var \Drupal\Core\Entity\EntityStorageInterface
    */
-  protected $storage;
+  protected $menuTree;
 
   /**
    * The database connection.
@@ -34,13 +34,13 @@ class MenuDeleteForm extends EntityConfirmFormBase {
   /**
    * Constructs a new MenuDeleteForm.
    *
-   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
-   *   The menu link storage.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   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(MenuLinkTreeInterface $menu_tree, Connection $connection) {
+    $this->menuTree = $menu_tree;
     $this->connection = $connection;
   }
 
@@ -49,7 +49,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('menu.link_tree'),
       $container->get('database')
     );
   }
@@ -78,7 +78,7 @@ public function getCancelRoute() {
    */
   public function getDescription() {
     $caption = '';
-    $num_links = $this->storage->countMenuLinks($this->entity->id());
+    $num_links = $this->menuTree->countMenuLinks($this->entity->id());
     if ($num_links) {
       $caption .= '<p>' . format_plural($num_links, '<strong>Warning:</strong> There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', '<strong>Warning:</strong> There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $this->entity->label())) . '</p>';
     }
@@ -104,21 +104,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->menuTree->loadLinksByRoute('menu_ui.menu_edit', array('menu' => $this->entity->id()), TRUE);
+    foreach ($menu_links as $id => $link) {
+      $this->menuTree->deleteLink($id);
+    }
 
     // Delete the custom menu and all its menu links.
     $this->entity->delete();
diff --git a/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuLinkEditForm.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuLinkEditForm.php
new file mode 100644
index 0000000..c9b240e
--- /dev/null
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuLinkEditForm.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_ui\Form\MenuLinkEditForm.
+ */
+
+namespace Drupal\menu_ui\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a generic edit form for all menu link plugin types.
+ */
+class MenuLinkEditForm extends FormBase {
+
+  /**
+   * The menu tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuTree;
+
+  /**
+   * Constructs a MenuLinkEditForm object.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   The menu tree service.
+   */
+  public function __construct(MenuLinkTreeInterface $menu_tree) {
+    $this->menuTree = $menu_tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('menu.link_tree')
+    );
+  }
+
+  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(),
+    );
+
+    $form['#plugin_form'] = $this->menuTree->getPluginForm($menu_link_plugin);
+
+    $form += $form['#plugin_form']->buildEditForm($form, $form_state);
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+      '#button_type' => 'primary',
+    );
+    return $form;
+  }
+
+  public function validateForm(array &$form, array &$form_state) {
+    $form['#plugin_form']->validateEditForm($form, $form_state);
+  }
+
+  public function submitForm(array &$form, array &$form_state) {
+    $link = $form['#plugin_form']->submitEditForm($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/lib/Drupal/menu_ui/Form/MenuLinkResetForm.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuLinkResetForm.php
index 31b23ab..2c74d53 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuLinkResetForm.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/Form/MenuLinkResetForm.php
@@ -7,18 +7,60 @@
 
 namespace Drupal\menu_ui\Form;
 
-use Drupal\Core\Entity\EntityConfirmFormBase;
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Defines a confirmation form for resetting a single modified menu link.
  */
-class MenuLinkResetForm extends EntityConfirmFormBase {
+class MenuLinkResetForm extends ConfirmFormBase {
+
+  /**
+   * The menu tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuTree;
+
+  /**
+   * The menu link.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkInterface
+   */
+  protected $link;
+
+  /**
+   * Constructs a MenuLinkEditForm object.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   The menu tree service.
+   */
+  public function __construct(MenuLinkTreeInterface $menu_tree) {
+    $this->menuTree = $menu_tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('menu.link_tree')
+    );
+  }
+
+  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()));
   }
 
   /**
@@ -28,7 +70,7 @@ public function getCancelRoute() {
     return array(
       'route_name' => 'menu_ui.menu_edit',
       'route_parameters' => array(
-        'menu' => $this->entity->menu_name,
+        'menu' => $this->link->getMenuName(),
       ),
     );
   }
@@ -37,28 +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) {
-    $new_menu_link = $this->entity->reset();
-    drupal_set_message(t('The menu link was reset to its default settings.'));
-    $form_state['redirect_route'] = array(
-      'route_name' => 'menu_ui.menu_edit',
-      'route_parameters' => array(
-        'menu' => $new_menu_link->menu_name,
-      ),
-    );
+  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->menuTree->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/lib/Drupal/menu_ui/MenuForm.php b/core/modules/menu_ui/lib/Drupal/menu_ui/MenuForm.php
index a796f66..ab7bd2f 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/MenuForm.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/MenuForm.php
@@ -11,9 +11,8 @@
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
 use Drupal\Core\Render\Element;
-use Drupal\menu_link\MenuLinkStorageInterface;
-use Drupal\menu_link\MenuTreeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -29,16 +28,9 @@ class MenuForm extends EntityForm {
   protected $entityQueryFactory;
 
   /**
-   * The menu link storage.
-   *
-   * @var \Drupal\menu_link\MenuLinkStorageInterface
-   */
-  protected $menuLinkStorage;
-
-  /**
    * The menu tree service.
    *
-   * @var \Drupal\menu_link\MenuTreeInterface
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
    */
   protected $menuTree;
 
@@ -54,14 +46,11 @@ 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\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, MenuLinkTreeInterface $menu_tree) {
     $this->entityQueryFactory = $entity_query_factory;
-    $this->menuLinkStorage = $menu_link_storage;
     $this->menuTree = $menu_tree;
   }
 
@@ -71,8 +60,7 @@ 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('menu.link_tree')
     );
   }
 
@@ -120,23 +108,6 @@ public function form(array $form, array &$form_state) {
       '#languages' => Language::STATE_ALL,
       '#default_value' => $menu->langcode,
     );
-    // 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()) {
@@ -169,7 +140,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();
+    return $this->menuTree->menuNameExists($value);
   }
 
   /**
@@ -230,6 +201,16 @@ public function save(array $form, array &$form_state) {
     $form_state['redirect_route'] = $this->entity->urlInfo('edit-form');
   }
 
+  protected function countElements($tree, $count = 0) {
+    foreach ($tree as $element) {
+      $count++;
+      if (!empty($element['below'])) {
+        $this->countElements($element['below'], $count);
+      }
+    }
+    return $count;
+  }
+
   /**
    * Form constructor to edit an entire menu tree at once.
    *
@@ -253,22 +234,12 @@ 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();
+    $tree = $this->menuTree->buildAllData($this->entity->id());
 
-    if (!empty($result)) {
-      $links = $this->menuLinkStorage->loadMultiple($result);
-    }
-
-    $delta = max(count($links), 50);
+    $count = $this->countElements($tree);
+    $delta = max($count, 50);
     // We indicate that a menu administrator is running the menu access check.
     $this->getRequest()->attributes->set('_menu_admin', TRUE);
-    $tree = $this->menuTree->buildTreeData($links);
     $this->getRequest()->attributes->set('_menu_admin', FALSE);
 
     $form = array_merge($form, $this->buildOverviewTreeForm($tree, $delta));
@@ -291,62 +262,61 @@ protected function buildOverviewForm(array &$form, array &$form_state) {
   protected function buildOverviewTreeForm($tree, $delta) {
     $form = &$this->overviewTreeForm;
     foreach ($tree as $data) {
+      /** @var \Drupal\Core\Menu\MenuLinkInterface $item */
       $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') . ')';
+      if ($item) {
+        $id = 'menu_plugin_id:' . $item->getPluginId();
+        $form[$id]['#item'] = $data;
+        $form[$id]['#attributes'] = $item->isHidden() ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled'));
+        $form[$id]['title']['#markup'] = \Drupal::linkGenerator()->generateFromUrl($item->getTitle(), $item->getUrlObject(), $item->getOptions());
+        if ($item->isHidden()) {
+          $form[$id]['title']['#markup'] .= ' (' . t('disabled') . ')';
         }
-        elseif ($item['link_path'] == 'user' && $item['module'] == 'user') {
-          $form[$mlid]['title']['#markup'] .= ' (' . t('logged in users only') . ')';
+        elseif (($url = $item->getUrlObject()) && !$url->isExternal() && $url->getRouteName() == 'user.page') {
+          $form[$id]['title']['#markup'] .= ' (' . 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' => t('Enable @title menu link', array('@title' => $item->getTitle())),
           '#title_display' => 'invisible',
-          '#default_value' => !$item['hidden'],
+          '#default_value' => !$item->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' => $item->getWeight(),
+          '#title' => t('Weight for @title', array('@title' => $item->getTitle())),
           '#title_display' => 'invisible',
         );
-        $form[$mlid]['mlid'] = array(
+        $form[$id]['id'] = array(
           '#type' => 'hidden',
-          '#value' => $item['mlid'],
+          '#value' => $item->getPluginId(),
         );
-        $form[$mlid]['plid'] = array(
+        $form[$id]['parent'] = array(
           '#type' => 'hidden',
-          '#default_value' => $item['plid'],
+          '#default_value' => $item->getParent(),
         );
         // Build a list of operations.
         $operations = array();
         $operations['edit'] = array(
           'title' => t('Edit'),
-          'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/edit',
+          'route_name' => 'menu_ui.link_edit',
+          'route_parameters' => array('menu_link_plugin' => $item->getPluginId()),
         );
-        // Only items created by the Menu UI module can be deleted.
-        if ($item->access('delete')) {
-          $operations['delete'] = array(
-            'title' => t('Delete'),
-            'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/delete',
-          );
-        }
-        // Set the reset column.
-        elseif ($item->access('reset')) {
+        // Links can either be reset or deleted, not both.
+        if ($item->isResetable()) {
           $operations['reset'] = array(
             'title' => t('Reset'),
-            'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/reset',
+            'route_name' => 'menu_ui.link_reset',
+            'route_parameters' => array('menu_link_plugin' => $item->getPluginId()),
           );
         }
-        $form[$mlid]['operations'] = array(
+        elseif ($delete_link = $item->getDeleteRoute()) {
+          $operations['delete'] = $delete_link;
+          $operations['delete']['title'] = t('Delete');
+        }
+        $form[$id]['operations'] = array(
           '#type' => 'operations',
           '#links' => $operations,
         );
@@ -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->menuTree->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/lib/Drupal/menu_ui/Tests/MenuCacheTagsTest.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuCacheTagsTest.php
index f2ef39d..25cd86a 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuCacheTagsTest.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/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' => '<front>',
-      'link_title' => 'Vicuña',
-      'menu_name' => 'llama',
-    ));
-    $menu_link->save();
+    /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+    $menu_tree = $this->container->get('menu.link_tree');
+    // Move a link into the new menu.
+    $menu_link = $menu_tree->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.
@@ -62,6 +60,7 @@ public function testMenuBlock() {
       'theme:stark',
       'theme_global_settings:1',
       'content:1',
+      'menu:account',
       'block_view:1',
       'block:' . $block->id(),
       'block_plugin:system_menu_block__llama',
@@ -80,10 +79,9 @@ public function testMenuBlock() {
     $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_tree->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.
@@ -91,11 +89,15 @@ public function testMenuBlock() {
 
 
     // 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' => '<front>',
-      'link_title' => 'Alpaca',
+    $menu_link_2 = entity_create('menu_link_content', array(
+      'id' => '',
+      'parent' => '',
+      'title' => 'Alpaca',
       'menu_name' => 'llama',
+      'route_name' => '<front>',
+      'bundle' => 'menu_name',
     ));
     $menu_link_2->save();
     $this->verifyPageCache($path, 'MISS');
@@ -104,22 +106,22 @@ public function testMenuBlock() {
     $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_tree->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();
     $this->verifyPageCache($path, 'MISS');
 
     // Verify a cache hit.
-    $this->verifyPageCache($path, 'HIT', array('content:1', 'theme:stark', 'theme_global_settings:1'));
+    $this->verifyPageCache($path, 'HIT', array('content:1', 'menu:account', 'theme:stark', 'theme_global_settings:1'));
   }
 
 }
diff --git a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuLanguageTest.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuLanguageTest.php
index 73374aa..617e2a6 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuLanguageTest.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuLanguageTest.php
@@ -85,8 +85,8 @@ 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.
@@ -94,7 +94,7 @@ function testMenuLanguage() {
     $menu_link = reset($menu_links);
     $this->assertMenuLink($menu_link->id(), array(
       'menu_name' => $menu_name,
-      'link_path' => $link_path,
+      'url' => $link_path,
       'langcode' => 'bb',
     ));
 
@@ -110,8 +110,8 @@ 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 new menu link default language.
@@ -119,7 +119,7 @@ function testMenuLanguage() {
     $menu_link = reset($menu_links);
     $this->assertMenuLink($menu_link->id(), array(
       'menu_name' => $menu_name,
-      'link_path' => $link_path,
+      'url' => $link_path,
       'langcode' => 'cc',
     ));
 
@@ -130,7 +130,7 @@ function testMenuLanguage() {
     $this->drupalPostForm('admin/structure/menu/item/' . $menu_link->id() . '/edit', $edit, t('Save'));
     $this->assertMenuLink($menu_link->id(), array(
       'menu_name' => $menu_name,
-      'link_path' => $link_path,
+      'url' => $link_path,
       'langcode' => 'bb',
     ));
 
diff --git a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuNodeTest.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuNodeTest.php
deleted file mode 100644
index 9c1c67e..0000000
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuNodeTest.php
+++ /dev/null
@@ -1,159 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\menu_ui\Tests\MenuNodeTest.
- */
-
-namespace Drupal\menu_ui\Tests;
-
-use Drupal\simpletest\WebTestBase;
-
-/**
- * Test menu settings for nodes.
- */
-class MenuNodeTest extends WebTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('menu_ui', 'test_page_test', 'node');
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Menu settings for nodes',
-      'description' => 'Add, edit, and delete a node with menu link.',
-      'group' => 'Menu',
-    );
-  }
-
-  function setUp() {
-    parent::setUp();
-    $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
-
-    $this->admin_user = $this->drupalCreateUser(array(
-      'access administration pages',
-      'administer content types',
-      'administer menu',
-      'create page content',
-      'edit any page content',
-      'delete any page content',
-    ));
-    $this->drupalLogin($this->admin_user);
-  }
-
-  /**
-   * Test creating, editing, deleting menu links via node form widget.
-   */
-  function testMenuNodeFormWidget() {
-    // Disable the default main menu, so that no menus are enabled.
-    $edit = array(
-      'menu_options[main]' => FALSE,
-    );
-    $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
-
-    // Verify that no menu settings are displayed and nodes can be created.
-    $this->drupalGet('node/add/page');
-    $this->assertText(t('Create Basic page'));
-    $this->assertNoText(t('Menu settings'));
-    $node_title = $this->randomName();
-    $edit = array(
-      'title[0][value]' => $node_title,
-      'body[0][value]' => $this->randomString(),
-    );
-    $this->drupalPostForm(NULL, $edit, t('Save'));
-    $node = $this->drupalGetNodeByTitle($node_title);
-    $this->assertEqual($node->getTitle(), $edit['title[0][value]']);
-
-    // Enable Tools menu as available menu.
-    $edit = array(
-      'menu_options[main]' => 1,
-      'menu_options[tools]' => 1,
-      'menu_parent' => 'main:0',
-    );
-    $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
-
-    // Create a node.
-    $node_title = $this->randomName();
-    $edit = array(
-      'title[0][value]' => $node_title,
-      'body[0][value]' => $this->randomString(),
-    );
-    $this->drupalPostForm('node/add/page', $edit, t('Save'));
-    $node = $this->drupalGetNodeByTitle($node_title);
-    // Assert that there is no link for the node.
-    $this->drupalGet('test-page');
-    $this->assertNoLink($node_title);
-
-    // Edit the node, enable the menu link setting, but skip the link title.
-    $edit = array(
-      'menu[enabled]' => 1,
-    );
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    // Assert that there is no link for the node.
-    $this->drupalGet('test-page');
-    $this->assertNoLink($node_title);
-
-    // Edit the node and create a menu link.
-    $edit = array(
-      'menu[enabled]' => 1,
-      'menu[link_title]' => $node_title,
-      'menu[weight]' => 17,
-    );
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    // Assert that the link exists.
-    $this->drupalGet('test-page');
-    $this->assertLink($node_title);
-
-    $this->drupalGet('node/' . $node->id() . '/edit');
-    $this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form');
-
-    // Edit the node and remove the menu link.
-    $edit = array(
-      'menu[enabled]' => FALSE,
-    );
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    // Assert that there is no link for the node.
-    $this->drupalGet('test-page');
-    $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),
-      'menu_name' => 'admin',
-    ));
-    $item->save();
-
-    // Assert that disabled Administration menu is not shown on the
-    // node/$nid/edit page.
-    $this->drupalGet('node/' . $node->id() . '/edit');
-    $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']);
-    $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);
-    // 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->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']);
-    // Assert that unallowed Administration menu is not available in options.
-    $this->assertNoOption('edit-menu-parent', 'admin:0');
-  }
-}
diff --git a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuTest.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuTest.php
index 078e26a..d0cce0b 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuTest.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuTest.php
@@ -82,9 +82,9 @@ 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();
+    $before_count = \Drupal::menuTree()->countMenuLinks(NULL);
     menu_link_rebuild_defaults();
-    $after_count = db_query('SELECT COUNT(*) FROM {menu_links}')->fetchField();
+    $after_count = \Drupal::menuTree()->countMenuLinks(NULL);
     $this->assertIdentical($before_count, $after_count, 'menu_link_rebuild_defaults() does not add more links');
     // Do standard user tests.
     // Login the user.
@@ -92,7 +92,7 @@ function testMenu() {
     $this->verifyAccess(403);
     foreach ($this->items as $item) {
       // Paths were set as 'node/$nid'.
-      $node = node_load(substr($item['link_path'], 5));
+      $node = node_load($item->getRouteParameters()['node']);
       $this->verifyMenuLink($item, $node);
     }
 
@@ -111,15 +111,13 @@ function testMenu() {
     $item = $this->getStandardMenuLink();
     $old_title = $item['link_title'];
     $this->modifyMenuLink($item);
-    $item = entity_load('menu_link', $item['mlid']);
+    $item = entity_load('menu_link_content', $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']);
+    $item->save();
+
+    $saved_item = entity_load('menu_link_content', $item['mlid']);
     $this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
     $this->resetMenuLink($item, $old_title);
   }
@@ -218,7 +216,7 @@ function deleteCustomMenu() {
     $this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $label)), 'Custom menu was deleted');
     $this->assertFalse(menu_ui_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.
@@ -249,32 +247,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(
+    $item1 = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name);
+    $item2 = $this->addMenuLink($item1->getPluginId(), 'node/' . $node2->id(), $menu_name, FALSE);
+    $item3 = $this->addMenuLink($item2->getPluginId(), 'node/' . $node3->id(), $menu_name);
+    $this->assertMenuLink($item1->getPluginId(), array(
       'depth' => 1,
       'has_children' => 1,
-      'p1' => $item1['mlid'],
+      'p1' => $item1->getPluginId(),
       'p2' => 0,
       // We assert the language code here to make sure that the language
       // selection element degrades gracefully without the Language module.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item2['mlid'], array(
+    $this->assertMenuLink($item2->getPluginId(), array(
       'depth' => 2, 'has_children' => 1,
-      'p1' => $item1['mlid'],
-      'p2' => $item2['mlid'],
+      'p1' => $item1->getPluginId(),
+      'p2' => $item2->getPluginId(),
       'p3' => 0,
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item3['mlid'], array(
+    $this->assertMenuLink($item3->getPluginId(), array(
       'depth' => 3,
       'has_children' => 0,
-      'p1' => $item1['mlid'],
-      'p2' => $item2['mlid'],
-      'p3' => $item3['mlid'],
+      'p1' => $item1->getPluginId(),
+      'p2' => $item2->getPluginId(),
+      'p3' => $item3->getPluginId(),
       'p4' => 0,
       // See above.
       'langcode' => 'en',
@@ -286,34 +284,35 @@ 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(
+    $item6 = $this->addMenuLink($item4->getPluginId(), 'node5', $menu_name, TRUE, '0', 'node/' . $node5->id());
+    $this->assertMenuLink($item4->getPluginId(), array(
       'depth' => 1,
       'has_children' => 1,
-      'p1' => $item4['mlid'],
+      'p1' => $item4->getPluginId(),
       'p2' => 0,
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item5['mlid'], array(
+    $this->assertMenuLink($item5->getPluginId(), array(
       'depth' => 2,
       'has_children' => 0,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
+      'p1' => $item4->getPluginId(),
+      'p2' => $item5->getPluginId(),
       'p3' => 0,
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item6['mlid'], array(
+    $this->assertMenuLink($item6->getPluginId(), array(
       'depth' => 2,
       'has_children' => 0,
-      'p1' => $item4['mlid'],
-      'p2' => $item6['mlid'],
+      'p1' => $item4->getPluginId(),
+      'p2' => $item6->getPluginId(),
       'p3' => 0,
-      'link_path' => 'node/' . $node5->id(),
+      'route_name' => 'node.view',
+      'route_parameters' => array('node' => $node5->id()),
       // See above.
       'langcode' => 'en',
     ));
@@ -327,49 +326,49 @@ 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(
+    $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name);
+    $this->assertMenuLink($item1->getPluginId(), array(
       'depth' => 1,
       'has_children' => 0,
-      'p1' => $item1['mlid'],
+      'p1' => $item1->getPluginId(),
       'p2' => 0,
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item4['mlid'], array(
+    $this->assertMenuLink($item4->getPluginId(), array(
       'depth' => 1,
       'has_children' => 1,
-      'p1' => $item4['mlid'],
+      'p1' => $item4->getPluginId(),
       'p2' => 0,
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item5['mlid'], array(
+    $this->assertMenuLink($item5->getPluginId(), array(
       'depth' => 2,
       'has_children' => 1,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
+      'p1' => $item4->getPluginId(),
+      'p2' => $item5->getPluginId(),
       'p3' => 0,
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item2['mlid'], array(
+    $this->assertMenuLink($item2->getPluginId(), array(
       'depth' => 3,
       'has_children' => 1,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
-      'p3' => $item2['mlid'],
+      'p1' => $item4->getPluginId(),
+      'p2' => $item5->getPluginId(),
+      'p3' => $item2->getPluginId(),
       'p4' => 0,
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item3['mlid'], array(
+    $this->assertMenuLink($item3->getPluginId(), array(
       'depth' => 4,
       'has_children' => 0,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
-      'p3' => $item2['mlid'],
-      'p4' => $item3['mlid'],
+      'p1' => $item4->getPluginId(),
+      'p2' => $item5->getPluginId(),
+      'p3' => $item2->getPluginId(),
+      'p4' => $item3->getPluginId(),
       'p5' => 0,
       // See above.
       'langcode' => 'en',
@@ -379,21 +378,19 @@ 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);
@@ -421,16 +418,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->getPluginId() . '/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->getPluginId() . '/edit', array('url' => $path), t('Save'));
+    $this->drupalGet('admin/structure/menu/item/' . $item->getPluginId() . '/edit');
+    $this->assertFieldByName('url', $path, 'Path no longer has query or fragment.');
   }
 
   /**
@@ -460,15 +457,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");
   }
 
   /**
@@ -494,42 +491,12 @@ public function testBlockContextualLinks() {
   }
 
   /**
-   * Tests menu link bundles.
-   */
-  public function testMenuBundles() {
-    $this->drupalLogin($this->admin_user);
-    $menu = $this->addCustomMenu();
-    // Clear the entity info cache to ensure the static caches are rebuilt.
-    entity_info_cache_clear();
-    $bundles = entity_get_bundles('menu_link');
-    $this->assertTrue(isset($bundles[$menu->id()]));
-    $menus = menu_list_system_menus();
-    $menus[$menu->id()] = $menu->label();
-    ksort($menus);
-    $this->assertIdentical(array_keys($bundles), array_keys($menus));
-
-    // Test if moving a menu link between menus changes the bundle.
-    $node = $this->drupalCreateNode(array('type' => 'article'));
-    $item = $this->addMenuLink(0, 'node/' . $node->id(), 'tools');
-    $this->moveMenuLink($item, 0, $menu->id());
-    $this->assertEqual($item->bundle(), 'tools', 'Menu link bundle matches the menu');
-
-    $moved_item = entity_load('menu_link', $item->id(), TRUE);
-    $this->assertNotEqual($moved_item->bundle(), $item->bundle(), 'Menu link bundle was changed');
-    $this->assertEqual($moved_item->bundle(), $menu->id(), 'Menu link bundle matches the menu');
-
-    $unsaved_item = entity_create('menu_link', array('menu_name' => $menu->id(), 'link_title' => $this->randomName(16), 'link_path' => '<front>'));
-    $this->assertEqual($unsaved_item->bundle(), $menu->id(), 'Unsaved menu link bundle matches the menu');
-    $this->assertEqual($unsaved_item->menu_name, $menu->id(), 'Unsaved menu link menu name matches the menu');
-  }
-
-  /**
    * Adds a menu link using the UI.
    *
-   * @param 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
@@ -538,40 +505,41 @@ public function testMenuBundles() {
    *   to FALSE.
    * @param string $weight
    *  Menu weight. Defaults to 0.
-   * @param string $actual_link
+   * @param bool|string $actual_path
    *   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 = '<front>', $menu_name = 'tools', $expanded = TRUE, $weight = '0', $actual_link = FALSE) {
+  function addMenuLink($parent = '', $path = '<front>', $menu_name = 'tools', $expanded = TRUE, $weight = '0', $actual_path = FALSE) {
     // View add menu link page.
     $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
     $this->assertResponse(200);
 
     $title = '!link_' . $this->randomName(16);
     $edit = array(
-      'link_path' => $link,
-      'link_title' => $title,
-      'description' => '',
+      'url' => $path,
+      'title[0][value]' => $title,
+      'description[0][value]' => '',
       'enabled' => TRUE,
       'expanded' => $expanded,
-      'parent' =>  $menu_name . ':' . $plid,
-      'weight' => $weight,
+      'menu_parent' =>  $menu_name . ':' . $parent,
+      'weight[0][value]' => $weight,
     );
 
-    if (!$actual_link) {
-      $actual_link = $link;
+    if (!$actual_path) {
+      $actual_path = $path;
     }
     // 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_content:' . $menu_link->uuid(), array('menu_name' => $menu_name, 'url' => $actual_path, 'has_children' => 0, 'parent' => $parent));
 
     return $menu_link;
   }
@@ -582,8 +550,8 @@ function addMenuLink($plid = 0, $link = '<front>', $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');
@@ -593,11 +561,11 @@ function addInvalidMenuLink() {
   /**
    * 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.
@@ -610,7 +578,7 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL)
     // 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.
@@ -620,7 +588,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.
@@ -632,18 +600,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\MenuLinkContent $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($item, $parent, $menu_name) {
+    $mlid = $item->getPluginId();
 
     $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);
@@ -652,37 +620,37 @@ function moveMenuLink($item, $plid, $menu_name) {
   /**
    * Modifies a menu link using the UI.
    *
-   * @param array $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link passed by reference.
    */
   function modifyMenuLink(&$item) {
-    $item['link_title'] = $this->randomName(16);
+    $item->title->value = $this->randomName(16);
 
-    $mlid = $item['mlid'];
-    $title = $item['link_title'];
+    $mlid = $item->getPluginId();
+    $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.');
+    $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
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link.
    * @param string $old_title
    *   Original title for menu link.
    */
   function resetMenuLink($item, $old_title) {
-    $mlid = $item['mlid'];
-    $title = $item['link_title'];
+    $mlid = $item->getPluginId();
+    $title = $item->getTitle();
 
     // Reset menu link.
     $this->drupalPostForm("admin/structure/menu/item/$mlid/reset", array(), t('Reset'));
@@ -698,12 +666,12 @@ function resetMenuLink($item, $old_title) {
   /**
    * 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'];
+    $mlid = $item->getPluginId();
+    $title = $item->getTitle();
 
     // Delete menu link.
     $this->drupalPostForm("admin/structure/menu/item/$mlid/delete", array(), t('Confirm'));
@@ -718,7 +686,7 @@ 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) {
@@ -726,25 +694,28 @@ function toggleMenuLink($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'];
+    $mlid = $item->getPluginId();
     $edit['enabled'] = FALSE;
     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
 
+    // Clear the internal cache of the menu tree in the test.
+    \Drupal::menuTree()->resetDefinitions();
+
     // Unlike most other modules, there is no confirmation message displayed.
     // Verify in the database.
     $this->assertMenuLink($mlid, array('hidden' => 1));
@@ -753,11 +724,11 @@ function disableMenuLink($item) {
   /**
    * Enables a menu link.
    *
-   * @param $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link.
    */
   function enableMenuLink($item) {
-    $mlid = $item['mlid'];
+    $mlid = $item->getPluginId();
     $edit['enabled'] = TRUE;
     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
 
@@ -793,7 +764,7 @@ 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')
+    $query = \Drupal::entityQuery('menu_link_content')
       ->condition('module', 'user')
       ->condition('machine_name', 'user.logout');
     $result = $query->execute();
@@ -804,7 +775,7 @@ private function getStandardMenuLink() {
     $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);
+    $item = entity_load('menu_link_content', $mlid);
     $this->assertTrue((bool) $item, 'Standard menu link was loaded');
     return $item;
   }
diff --git a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuWebTestBase.php b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuWebTestBase.php
index a8dda4b..012b38e 100644
--- a/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuWebTestBase.php
+++ b/core/modules/menu_ui/lib/Drupal/menu_ui/Tests/MenuWebTestBase.php
@@ -19,28 +19,77 @@ class MenuWebTestBase extends WebTestBase {
    *
    * @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\MenuLinkInterface $menu_link */
+    $menu_tree = $this->container->get('menu.link_tree');
+    $menu_tree->resetDefinitions();
+    $definition = $menu_tree->getDefinition($menu_plugin_id);
+
+//    $options = $definition['options'];
+
+    // 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 */
+      $link = reset($links);
+      $url_object = $link->getUrlObject();
+
+      // Force to not convert to aliases for the test.
+      if ($url_object->isExternal()) {
+        $url = \Drupal::urlGenerator()->generateFromPath($url_object->getPath(), $url_object->getOptions());
+      }
+      else {
+        $url = \Drupal::url($url_object->getRouteName(), $url_object->getRouteParameters(), $url_object->getOptions() + array('alias' => TRUE));
+      }
+      if (!$url_object->isExternal()) {
+        $base_url = \Drupal::service('router.request_context')->getBaseUrl();
+        $url = substr($url, strlen($base_url) + 1);
+      }
+      $definition['url'] = $url;
+
+      // Replace
+      if (isset($expected_item['url']) && $expected_item['url'] == '<front>') {
+        $expected_item['url'] = '';
+      }
     }
-    if (!empty($options['fragment'])) {
-      $item['link_path'] .= '#' . $options['fragment'];
+
+    // The internal storage of the tree does use some INTs instead of the plugin
+    // ids. Therefore first figure out the INTs for p1...p9.
+    $raw_menu_tree_entry = \Drupal::database()->query("SELECT * FROM {menu_tree} WHERE id = :id", array(':id' => $menu_plugin_id))->fetchAssoc();
+    for ($i = 1; $i <= 9; $i++) {
+      if (isset($expected_item['p' . $i])) {
+        // Get the internal MLID from the plugin ID.
+        $mlid = \Drupal::database()->query("SELECT mlid FROM {menu_tree} where id = :id", array(':id' => $expected_item['p' . $i]))->fetchField();
+        $this->assertEqual($mlid, $raw_menu_tree_entry['p' . $i]);
+        unset($expected_item['p' . $i]);
+      }
     }
+    foreach (array('depth', 'has_children') as $internal_key) {
+      if (isset($expected_item[$internal_key])) {
+        $this->assertEqual($raw_menu_tree_entry[$internal_key], $expected_item[$internal_key]);
+        unset($expected_item[$internal_key]);
+      }
+    }
+
+    if (isset($expected_item['langcode'])) {
+      $this->assertEqual($link->langcode->value, $expected_item['langcode']);
+      unset($expected_item['langcode']);
+    }
+
     foreach ($expected_item as $key => $value) {
-      $this->assertEqual($item[$key], $value);
+      $this->assertEqual($definition[$key], $value);
     }
   }
 
diff --git a/core/modules/menu_ui/menu_ui.admin.inc b/core/modules/menu_ui/menu_ui.admin.inc
index 0d7a0fd..2021be0 100644
--- a/core/modules/menu_ui/menu_ui.admin.inc
+++ b/core/modules/menu_ui/menu_ui.admin.inc
@@ -5,6 +5,7 @@
  * Administrative page callbacks for Menu UI module.
  */
 
+use Drupal\Core\Menu\MenuLinkTreeInterface;
 use Drupal\Core\Render\Element;
 
 /**
@@ -27,17 +28,17 @@ 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';
+      $element['parent']['#type'] = 'hidden';
 
       $indent = array(
         '#theme' => 'indentation',
@@ -46,8 +47,8 @@ function theme_menu_overview_form($variables) {
 
       $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 +72,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..60432d5 100644
--- a/core/modules/menu_ui/menu_ui.local_actions.yml
+++ b/core/modules/menu_ui/menu_ui.local_actions.yml
@@ -1,5 +1,5 @@
-menu_ui_link_add:
-  route_name: menu_ui.link_add
+menu_ui.link_add:
+  route_name: menu_link_content.link_add
   title: 'Add link'
   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 b12baee..add5077 100644
--- a/core/modules/menu_ui/menu_ui.module
+++ b/core/modules/menu_ui/menu_ui.module
@@ -11,15 +11,12 @@
  * 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\Render\Element;
+use Drupal\node\NodeInterface;
 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;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -75,31 +72,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().
@@ -169,7 +146,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());
+  /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu.link_tree');
+  $menu_tree->deleteLinksInMenu($menu->id());
 
   // Remove menu from active menus variable.
   $config = \Drupal::config('system.menu');
@@ -198,113 +177,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),
+      );
+    }
   }
 }
 
@@ -322,7 +206,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) {
@@ -331,7 +215,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();
 }
 
@@ -345,29 +229,34 @@ 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)) {
+    /** @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface $link */
     $link = &$node->menu;
-    if (empty($link['enabled'])) {
+    if (!$link->enabled) {
       if (!$link->isNew()) {
-        menu_link_delete($link['mlid']);
+        $link->delete();
       }
     }
-    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']);
+    elseif (trim($link->getTitle())) {
+      $link->title->value = trim($link->getTitle());
+      $link->route_name->value = 'node.view';
+      $link->setRouteParameters(array('node' => $node->id()));
+      $options = $link->getOptions();
+      if (trim($link->getDescription())) {
+        $options['attributes']['title'] = trim($link->getDescription());
       }
       else {
         // If the description field was left empty, remove the title attribute
         // from the menu link.
-        unset($link['options']['attributes']['title']);
+        unset($options['attributes']['title']);
       }
-      if (!menu_link_save($link)) {
+      $link->setOptions($options);
+      if (!$link->save()) {
         drupal_set_message(t('There was an error saving the menu link.'), 'error');
       }
     }
@@ -379,13 +268,14 @@ function menu_ui_node_save(EntityInterface $node) {
  */
 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');
+  $query = \Drupal::entityQuery('menu_link_content')
+    ->condition('route_name', 'node.view')
+    ->condition('route_parameters', serialize(array('node' => $node->id())));
   $result = $query->execute();
 
   if (!empty($result)) {
-    menu_link_delete_multiple($result);
+    $menu_link = \Drupal::entityManager()->getStorage('menu_link_content')->load(reset($result));
+    $menu_link->delete();
   }
 }
 
@@ -399,58 +289,48 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, array &$form
     $menu_name = strtok($node_type_config->get('parent'), ':');
     $menu_link = 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);
       }
     }
 
     if (!$menu_link) {
-      $menu_link = entity_create('menu_link', array(
-        'mlid' => 0,
-        'plid' => 0,
+      $menu_link = entity_create('menu_link_content', array(
+        'id' => 0,
+        'parent' => '',
         'menu_name' => $menu_name,
+        'bundle' => 'menu_link_content',
       ));
     }
     // 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);
-  }
-}
-
-/**
- * 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);
 }
 
 /**
@@ -464,9 +344,17 @@ 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();
+  /** @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface $link */
   $link = $node->menu;
   $type = $node->getType();
-  $options = menu_ui_parent_options(menu_ui_get_menus(), $link, $type);
+  /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $link_tree */
+  $link_tree = \Drupal::menuTree();
+  $available_menus = menu_ui_get_menus();
+  $type_menus = \Drupal::config("menu.entity.node.$type")->get('available_menus');
+  foreach ($type_menus as $menu) {
+    $available_menus[$menu] = $menu;
+  }
+  $options = $link_tree->getParentSelectOptions($link->getPluginId(), $available_menus);
   // If no possible parent menu items were found, there is nothing to display.
   if (empty($options)) {
     return;
@@ -476,7 +364,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) $link->getTitle(),
     '#group' => 'advanced',
     '#attached' => array(
       'library' => array('menu/drupal.menu'),
@@ -488,7 +376,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) $link->id(),
   );
   $form['menu']['link'] = array(
     '#type' => 'container',
@@ -501,26 +389,26 @@ 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', 'hidden', 'options', 'expanded', 'bundle') as $key) {
+    $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $link->{$key}->value);
   }
 
-  $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' => $link->getTitle(),
   );
 
   $form['menu']['link']['description'] = array(
     '#type' => 'textarea',
     '#title' => t('Description'),
-    '#default_value' => isset($link['options']['attributes']['title']) ? $link['options']['attributes']['title'] : '',
+    '#default_value' => isset($link->getOptions()['attributes']['title']) ? $link->getOptions()['attributes']['title'] : '',
     '#rows' => 1,
     '#description' => t('Shown when hovering over the menu link.'),
   );
 
-  if ($link['mlid']) {
-    $default = $link['menu_name'] . ':' . $link['plid'];
+  if ($link->id()) {
+    $default = $link->getMenuName() . ':' . $link->getParent();
   }
   else {
     $default = \Drupal::config('menu.entity.node.'.$type)->get('parent');
@@ -542,7 +430,7 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
   );
 
   // Get number of items in menu so the weight selector is sized appropriately.
-  $delta = entity_get_controller('menu_link')->countMenuLinks($link->menu_name);
+  $delta = $link_tree->countMenuLinks($link->getMenuName());
   if ($delta < 50) {
     // Old hardcoded value
     $delta = 50;
@@ -551,7 +439,7 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
     '#type' => 'weight',
     '#title' => t('Weight'),
     '#delta' => $delta,
-    '#default_value' => $link['weight'],
+    '#default_value' => $link->getWeight(),
     '#description' => t('Menu links with lower weights are displayed before links with higher weights.'),
   );
 }
@@ -564,7 +452,7 @@ 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']);
+    $node->menu = entity_create('menu_link_content', $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.
@@ -574,7 +462,7 @@ function menu_ui_node_submit(EntityInterface $node, $form, $form_state) {
     // 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']);
+      list($node->menu->menu_name->value, $node->menu->parent->value) = explode(':', $form_state['values']['menu']['parent']);
     }
   }
 }
@@ -588,6 +476,8 @@ 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\MenuLinkTreeInterface $link_tree */
+  $link_tree = \Drupal::menuTree();
   $menu_options = menu_ui_get_menus();
   $type = $form_state['controller']->getEntity();
   if ($type->id()) {
@@ -618,8 +508,7 @@ function menu_ui_form_node_type_form_alter(&$form, $form_state) {
   // 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);
+  $options = $link_tree->getParentSelectOptions('');
   $form['menu']['menu_parent'] = array(
     '#type' => 'select',
     '#title' => t('Default parent item'),
diff --git a/core/modules/menu_ui/menu_ui.routing.yml b/core/modules/menu_ui/menu_ui.routing.yml
index 4be3bbf..fa1db94 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/item/{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/item/{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/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/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml
index 690a3ec..2adbbc2 100644
--- a/core/modules/system/config/schema/system.schema.yml
+++ b/core/modules/system/config/schema/system.schema.yml
@@ -390,3 +390,30 @@ system.mail:
 system.theme.global:
   type: theme_settings
   label: 'Theme global settings'
+
+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/lib/Drupal/system/Controller/AdminController.php b/core/modules/system/lib/Drupal/system/Controller/AdminController.php
index 7445d52..d9800f0 100644
--- a/core/modules/system/lib/Drupal/system/Controller/AdminController.php
+++ b/core/modules/system/lib/Drupal/system/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/lib/Drupal/system/Controller/SystemController.php b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
index 6227296..2953f21 100644
--- a/core/modules/system/lib/Drupal/system/Controller/SystemController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Controller;
 
+use Drupal\Core\Menu\MenuLinkInterface;
 use Drupal\Component\Serialization\Json;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Entity\Query\QueryFactory;
@@ -102,48 +103,31 @@ public function overview() {
     if ($this->systemManager->checkRequirements() && $this->currentUser()->hasPermission('administer site configuration')) {
       drupal_set_message($this->t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'error');
     }
+    /* @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+    $menu_tree = \Drupal::service('plugin.manager.menu.link_tree');
+    $system_link = $menu_tree->createInstance('system.admin_config');
+    // Only find the children of this link.
+    $parameters['expanded'][] = 'system.admin_config';
+    $parameters['conditions']['hidden'] = 0;
+    $tree =  $menu_tree->buildTree($system_link->getMenuName(), $parameters);
     $blocks = array();
     // Load all links on admin/config and menu links below it.
-    $query = $this->queryFactory->get('menu_link')
-      ->condition('link_path', 'admin/config')
-      ->condition('module', 'system');
-    $result = $query->execute();
-    $menu_link_storage = $this->entityManager()->getStorage('menu_link');
-    if ($system_link = $menu_link_storage->loadMultiple($result)) {
-      $system_link = reset($system_link);
-      $query = $this->queryFactory->get('menu_link')
-        ->condition('link_path', 'admin/help', '<>')
-        ->condition('menu_name', $system_link->menu_name)
-        ->condition('plid', $system_link->id())
-        ->condition('hidden', 0);
-      $result = $query->execute();
-      if (!empty($result)) {
-        $menu_links = $menu_link_storage->loadMultiple($result);
-        foreach ($menu_links as $item) {
-          _menu_link_translate($item);
-          if (!$item['access']) {
-            continue;
-          }
-          // The link description, either derived from 'description' in hook_menu()
-          // or customized via Menu UI module is used as title attribute.
-          if (!empty($item['localized_options']['attributes']['title'])) {
-            $item['description'] = $item['localized_options']['attributes']['title'];
-            unset($item['localized_options']['attributes']['title']);
-          }
-          $block = $item;
-          $block['content'] = array(
-            '#theme' => 'admin_block_content',
-            '#content' => $this->systemManager->getAdminBlock($item),
-          );
 
-          if (!empty($block['content']['#content'])) {
-            // 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;
-          }
-        }
+    foreach ($tree as $key => $item) {
+      $block['title'] = $item['link']->getTitle();
+      $block['description'] = $item['link']->getDescription();
+      $block['content'] = array(
+        '#theme' => 'admin_block_content',
+        '#content' => $this->systemManager->getAdminBlock($item['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[$key] = $block;
       }
     }
+
     if ($blocks) {
       ksort($blocks);
       return array(
diff --git a/core/modules/system/lib/Drupal/system/Form/ModulesListForm.php b/core/modules/system/lib/Drupal/system/Form/ModulesListForm.php
index d061495..629d6d6 100644
--- a/core/modules/system/lib/Drupal/system/Form/ModulesListForm.php
+++ b/core/modules/system/lib/Drupal/system/Form/ModulesListForm.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Form;
 
 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;
@@ -16,9 +17,11 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
 use Drupal\Core\Render\Element;
+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.
@@ -59,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;
 
   /**
    * {@inheritdoc}
@@ -74,8 +84,9 @@ public static function create(ContainerInterface $container) {
       $container->get('keyvalue.expirable')->get('module_list'),
       $container->get('access_manager'),
       $container->get('entity.manager'),
-      $container->get('entity.query'),
-      $container->get('current_user')
+      $container->get('current_user'),
+      $container->get('title_resolver'),
+      $container->get('router.route_provider')
     );
   }
 
@@ -90,18 +101,21 @@ public static function create(ContainerInterface $container) {
    *   Access manager.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
-   *   The entity query factory.
    * @param \Drupal\Core\Session\AccountInterface $current_user
    *   The current user.
+   * @param \Drupal\Core\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) {
+  public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManager $access_manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, 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->titleResolver = $title_resolver;
+    $this->routeProvider = $route_provider;
   }
 
   /**
@@ -236,11 +250,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'),
@@ -249,7 +265,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/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
index a698e1f..5646bf1 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
@@ -10,7 +10,7 @@
 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 Symfony\Component\DependencyInjection\ContainerInterface;
 
 
@@ -29,7 +29,7 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa
   /**
    * The menu tree.
    *
-   * @var \Drupal\menu_link\MenuTreeInterface
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
    */
   protected $menuTree;
 
@@ -42,10 +42,10 @@ 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
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
    *   The menu tree.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuTreeInterface $menu_tree) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->menuTree = $menu_tree;
   }
@@ -58,7 +58,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('menu_link.tree')
+      $container->get('plugin.manager.menu.link_tree')
     );
   }
 
@@ -66,8 +66,9 @@ public static function create(ContainerInterface $container, array $configuratio
    * {@inheritdoc}
    */
   public function build() {
-    $menu = $this->getDerivativeId();
-    return $this->menuTree->renderMenu($menu);
+    $menu_name = $this->getDerivativeId();
+    $tree = $this->menuTree->buildPageData($menu_name);
+    return $this->menuTree->buildRenderTree($tree);
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/SystemManager.php b/core/modules/system/lib/Drupal/system/SystemManager.php
index 99b928b..0f9435c 100644
--- a/core/modules/system/lib/Drupal/system/SystemManager.php
+++ b/core/modules/system/lib/Drupal/system/SystemManager.php
@@ -7,6 +7,7 @@
 namespace Drupal\system;
 
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Menu\MenuLinkInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Drupal\Core\Database\Connection;
@@ -83,7 +84,7 @@ class SystemManager {
   public function __construct(ModuleHandlerInterface $module_handler, Connection $database, EntityManagerInterface $entity_manager, RequestStack $request_stack) {
     $this->moduleHandler = $module_handler;
     $this->database = $database;
-    $this->menuLinkStorage = $entity_manager->getStorage('menu_link');
+    //$this->menuLinkStorage = $entity_manager->getStorage('menu_link');
     $this->requestStack = $request_stack;
   }
 
@@ -173,9 +174,11 @@ public function getMaxSeverity(&$requirements) {
   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)) {
+    /* @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+    $menu_tree = \Drupal::service('plugin.manager.menu.link_tree');
+    $route_parameters = $request->attributes->get('_raw_variables')->all();
+    $instance = $menu_tree->menuLinkGetPreferred($route_name, $route_parameters);
+    if ($instance && $content = $this->getAdminBlock($instance)) {
       $output = array(
         '#theme' => 'admin_block_content',
         '#content' => $content,
@@ -192,48 +195,30 @@ 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.
+    $parameters['expanded'][] = $instance->getPluginId();
+    $parameters['conditions']['hidden'] = 0;
+    /* @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+    $menu_tree = \Drupal::service('plugin.manager.menu.link_tree');
+    $tree =  $menu_tree->buildTree($instance->getMenuName(), $parameters);
+    foreach ($tree as $key => $item) {
+      /** @var $link \Drupal\Core\Menu\MenuLinkInterface */
+      $link = $item['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;
+    //$this->menuItems[$item['mlid']] = $content;
     return $content;
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
index fdf16b9..bd8df1b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
@@ -68,6 +68,7 @@ function testPageCacheTags() {
     sort($cache_entry->tags);
     $expected_tags = array(
       'content:1',
+      'menu:account',
       'pre_render:1',
       'system_test_cache_tags_page:1',
       'theme:stark',
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
index f310b24..b6876c2 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -94,6 +94,7 @@ function testPageCacheTags() {
       'node:' . $node_1->id(),
       'user:' . $author_1->id(),
       'filter_format:basic_html',
+      'menu:account',
       'menu:tools',
       'menu:footer',
       'menu:main',
@@ -121,6 +122,7 @@ function testPageCacheTags() {
       'node:' . $node_2->id(),
       'user:' . $author_2->id(),
       'filter_format:full_html',
+      'menu:account',
       'menu:tools',
       'menu:footer',
       'menu:main',
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
index 7e6506b..db6a609 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
@@ -225,7 +225,7 @@ public function testReferencedEntity() {
     $non_referencing_entity_path = $this->non_referencing_entity->getSystemPath();
     $listing_path = 'entity_test/list/' . $entity_type . '_reference/' . $entity_type . '/' . $this->entity->id();
 
-    $theme_cache_tags = array('content:1', 'theme:stark', 'theme_global_settings:1');
+    $theme_cache_tags = array('content:1', 'menu:account', 'theme:stark', 'theme_global_settings:1');
 
     // Generate the standardized entity cache tags.
     $cache_tag = $entity_type . ':' . $this->entity->id();
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
index 849aadc..1510f65 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
@@ -88,15 +88,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/item/node.add_page/edit", $trail);
     $this->assertBreadcrumb('admin/structure/menu/manage/tools/add', $trail);
 
     // Verify Node administration breadcrumbs.
@@ -179,12 +174,13 @@ function testBreadCrumbs() {
       $node2 = $this->drupalCreateNode(array(
         'type' => $type,
         'title' => $title,
-        'menu' => entity_create('menu_link', array(
+        'menu' => entity_create('menu_link_content', array(
           'enabled' => 1,
-          'link_title' => 'Parent ' . $title,
+          'title' => 'Parent ' . $title,
           'description' => '',
           'menu_name' => $menu,
-          'plid' => 0,
+          'parent' => '',
+          'bundle' => 'menu_link_content',
         )),
       ));
 
@@ -197,19 +193,19 @@ 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[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(
@@ -250,15 +246,14 @@ function testBreadCrumbs() {
     foreach ($tags as $name => $data) {
       $term = $data['term'];
       $edit = array(
-        'link_title' => "$name link",
-        'link_path' => "taxonomy/term/{$term->id()}",
+        'title[0][value]' => "$name link",
+        'url' => "taxonomy/term/{$term->id()}",
         'parent' => "$menu:{$parent_mlid}",
       );
       $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
-      $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $edit['link_title'], 'link_path' => $edit['link_path']));
+      $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $edit['link_title'], '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.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
index d0db0de..2f49ef7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
+++ b/core/modules/system/lib/Drupal/system/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 maanger
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface $menuTree
+   */
+  protected $menuTree;
 
   public static function getInfo() {
     return array(
@@ -36,6 +45,8 @@ public static function getInfo() {
   public function setUp() {
     parent::setUp();
 
+    $this->menuTree = $this->container->get('menu.link_tree');
+
     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->menuTree->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->menuTree->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->menuTree->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->menuTree->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,22 +166,19 @@ 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);
-    }
+    // have been reassigned to the parent.
+    $links = $this->createLinkHierarchy($module);
+    $this->menuTree->deleteLink($links['child-1']);
+
+    $expected_hierarchy = array(
+      'parent' => FALSE,
+      'child-1-1' => 'parent',
+      'child-1-2' => 'parent',
+      'child-2' => 'parent',
+    );
+    $this->assertMenuLinkParents($links, $expected_hierarchy);
 
+    return;
     // 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
@@ -195,102 +212,21 @@ function testMenuLinkReparenting($module = 'menu_test') {
   }
 
   /**
-   * 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->menuTree->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->menuTree->loadLinksByRoute('menu_test.menu_test');
     $this->assertEqual(count($menu_links), 0);
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterRebuildTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterRebuildTest.php
deleted file mode 100644
index 59d0f50..0000000
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterRebuildTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\system\Tests\Menu\MenuRouterRebuildTest.
- */
-
-namespace Drupal\system\Tests\Menu;
-
-use Drupal\simpletest\WebTestBase;
-use Drupal\Core\Language\Language;
-
-/**
- * Tests menu_router_rebuild().
- */
-class MenuRouterRebuildTest extends WebTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('language', 'menu_test');
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Menu router rebuild',
-      'description' => 'Tests menu_router_rebuild().',
-      'group' => 'Menu',
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  function setUp() {
-    parent::setUp();
-
-    $language = new Language(array('id' => 'nl'));
-    language_save($language);
-  }
-
-  /**
-   * Tests configuration context when rebuilding the menu router table.
-   */
-  public function testMenuRouterRebuildContext() {
-    // Enter a language context before rebuilding the menu router tables.
-    \Drupal::languageManager()->setConfigOverrideLanguage(language_load('nl'));
-    \Drupal::service('router.builder')->rebuild();
-
-    // Check that the language context was not used for building the menu item.
-    $menu_items = \Drupal::entityManager()->getStorage('menu_link')->loadByProperties(array('route_name' => 'menu_test.context'));
-    $menu_item = reset($menu_items);
-    $this->assertTrue($menu_item['link_title'] == 'English', 'Config context overrides are ignored when rebuilding menu router items.');
-  }
-
-}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTreeStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTreeStorageTest.php
new file mode 100644
index 0000000..0918d80
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTreeStorageTest.php
@@ -0,0 +1,298 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Menu\MenuTreeStorageTest.
+ */
+
+namespace Drupal\system\Tests\Menu;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Menu\MenuLinkTreeStorage;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+class MenuTreeStorageTest extends DrupalUnitTestBase {
+
+  /**
+   * @var \Drupal\Core\Menu\MenuLinkTreeStorage
+   */
+  protected $treeStorage;
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('system');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Menu tree storage tests',
+      'description' => 'Tests menu tree storage tests',
+      'group' => 'Menu'
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->treeStorage = new MenuLinkTreeStorage($this->container->get('database'), $this->container->get('url_generator'));
+    $this->connection = $this->container->get('database');
+  }
+
+
+  public function testBasicMethods() {
+    $this->doTestEmptyStorage();
+    $this->doTestTable();
+  }
+
+  public function doTestEmptyStorage() {
+    $this->assertEqual(0, $this->treeStorage->countMenuLinks());
+  }
+
+  public function doTestTable() {
+    // Test that we can create a tree storage with an arbitrary table name and
+    // that selecting from the storage creates the table.
+    $tree_storage = new MenuLinkTreeStorage($this->container->get('database'), $this->container->get('url_generator'), 'test_menu_tree');
+    $this->assertFalse($this->connection->schema()->tableExists('test_menu_tree'), 'Test table is not yet created');
+    $tree_storage->countMenuLinks();
+    $this->assertTrue($this->connection->schema()->tableExists('test_menu_tree'), 'Test table was created');
+  }
+
+  public function testSimpleHierarchy() {
+    // Add some links with parent on the previous one and test some values.
+    // <tools>
+    // - test1
+    // -- test2
+    // --- test3
+    $this->addMenuLink('test1', '');
+    $this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
+
+    $this->addMenuLink('test2', 'test1');
+    $this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test2'));
+    $this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 2), array('test1'));
+
+    $this->addMenuLink('test3', 'test2');
+    $this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test2', 'test3'));
+    $this->assertMenuLink('test2', array('has_children' => 1, 'depth' => 2), array('test1'), array('test3'));
+    $this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 3), array('test2', 'test1'));
+  }
+
+  public function testMenuLinkMoving() {
+    // Before the move.
+    // <tools>
+    // - test1
+    // -- test2
+    // --- test3
+    // - test4
+    // -- test5
+    // --- test6
+
+    $this->addMenuLink('test1', '');
+    $this->addMenuLink('test2', 'test1');
+    $this->addMenuLink('test3', 'test2');
+    $this->addMenuLink('test4', '');
+    $this->addMenuLink('test5', 'test4');
+    $this->addMenuLink('test6', 'test5');
+
+    $this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test2', 'test3'));
+    $this->assertMenuLink('test2', array('has_children' => 1, 'depth' => 2), array('test1'), array('test3'));
+    $this->assertMenuLink('test4', array('has_children' => 1, 'depth' => 1), array(),  array('test5', 'test6'));
+    $this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 2), array('test4'), array('test6'));
+    $this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 3), array('test5', 'test4'));
+
+    $this->moveMenuLink('test2', 'test5');
+    // After the 1st move.
+    // <tools>
+    // - test1
+    // - test4
+    // -- test5
+    // --- test2
+    // ---- test3
+    // --- test6
+
+    $this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
+    $this->assertMenuLink('test2', array('has_children' => 1, 'depth' => 3), array('test5', 'test4'), array('test3'));
+    $this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 4), array('test2', 'test5', 'test4'));
+    $this->assertMenuLink('test4', array('has_children' => 1, 'depth' => 1), array(), array('test5', 'test2', 'test3', 'test6'));
+    $this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 2), array('test4'), array('test2', 'test3', 'test6'));
+    $this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 3), array('test5', 'test4'));
+
+    $this->moveMenuLink('test4', 'test1');
+    $this->moveMenuLink('test3', 'test1');
+    // After the next 2 moves.
+    // <tools>
+    // - test1
+    // -- test3
+    // -- test4
+    // --- test5
+    // ---- test2
+    // ---- test6
+
+    $this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test4', 'test5', 'test2', 'test3', 'test6'));
+    $this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 4), array('test5', 'test4', 'test1'));
+    $this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 2), array('test1'));
+    $this->assertMenuLink('test4', array('has_children' => 1, 'depth' => 2), array('test1'), array('test2', 'test5', 'test6'));
+    $this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 3), array('test4', 'test1'), array('test2', 'test6'));
+    $this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 4), array('test5', 'test4', 'test1'));
+
+    // Deleting a link in the middle should re-attach child links to the parent
+    $this->treeStorage->delete('test4');
+    // After the delete.
+    // <tools>
+    // - test1
+    // -- test3
+    // -- test5
+    // --- test2
+    // --- test6
+    $this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test5', 'test2', 'test3', 'test6'));
+    $this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 3), array('test5', 'test1'));
+    $this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 2), array('test1'));
+    $this->assertFalse($this->treeStorage->load('test4'));
+    $this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 2), array('test1'), array('test2', 'test6'));
+    $this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 3), array('test5', 'test1'));
+  }
+
+  public function testMenuHiddenChildLinks() {
+    // Add some links with parent on the previous one and test some values.
+    // <tools>
+    // - test1
+    // -- test2 (hidden)
+
+    $this->addMenuLink('test1', '');
+    $this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
+
+    $this->addMenuLink('test2', 'test1', '<front>', array(), 'tools', array('hidden' => 1));
+    // The 1st link does not have any visible children, so has_children is still 0.
+    $this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
+    $this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 2, 'hidden' => 1), array('test1'));
+
+    // Add more links with parent on the previous one.
+    // <footer>
+    // - footerA
+    //  ===============
+    // <tools>
+    // - test1
+    // -- test2 (hidden)
+    // --- test3
+    // ---- test4
+    // ----- test5
+    // ------ test6
+    // ------- test7
+    // -------- test8
+    // --------- test9
+    $this->addMenuLink('footerA', '', '<front>', array(), 'footer');
+    $visible_children = array();
+    for ($i = 3; $i <= $this->treeStorage->maxDepth(); $i++) {
+      $parent = $i - 1;
+      $this->addMenuLink("test$i", "test$parent");
+      $visible_children[] = "test$i";
+    }
+    // The 1st link does not have any visible children, so has_children is still
+    // 0. However, it has visible children that will be found.
+    $this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1), array(), $visible_children);
+    // This should fail since test9 would end up at greater than max depth
+    try {
+      $this->moveMenuLink('test1', 'footerA');
+      $this->fail('Exception was not thrown');
+    }
+    catch (PluginException $e) {
+      $this->pass($e->getMessage());
+    }
+    // The opposite move should work, and change the has_children flag.
+    $this->moveMenuLink('footerA', 'test1');
+    $visible_children[] = 'footerA';
+    $this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), $visible_children);
+  }
+
+  /**
+   * Add a link with the given ID and supply defaults.
+   */
+  protected function addMenuLink($id, $parent = '', $route_name = 'test', $route_parameters = array(), $menu_name = 'tools', $extra = array()) {
+    $link = array(
+      'id' => $id,
+      'menu_name' => $menu_name,
+      'route_name' => $route_name,
+      'route_parameters' => $route_parameters,
+      'title_arguments' => array(),
+      'title' => 'test',
+      'parent' => $parent,
+      'options' => array(),
+      'metadata' => array(),
+    ) + $extra;
+    $this->treeStorage->save($link);
+  }
+
+  /**
+   * Move the link with the given ID so it's under a new parent.
+   */
+  protected function moveMenuLink($id, $new_parent) {
+    $menu_link = $this->treeStorage->load($id);
+    $menu_link['parent'] = $new_parent;
+    $this->treeStorage->save($menu_link);
+  }
+
+  /**
+   * Test that a link's stored representation matches the expected values.
+   *
+   * @param $id
+   *   The ID of the menu link to test
+   * @param array $expected_properties
+   *   A keyed array of column names and values like has_children and depth.
+   * @param array $parents
+   *   An ordered array of the IDs of the menu links that are the parents.
+   * @param array $children
+   *   Array of child IDs that are visible (hidden == 0).
+   */
+  protected function assertMenuLink($id, array $expected_properties, array $parents = array(), array $children = array()) {
+    $query = $this->connection->select('menu_tree');
+    $query->fields('menu_tree');
+    $query->condition('id', $id);
+    foreach ($expected_properties as $field => $value) {
+      $query->condition($field, $value);
+    }
+    $all = $query->execute()->fetchAll(\PDO::FETCH_ASSOC);
+    $this->assertEqual(count($all), 1, "Found link $id matching all the expected properties");
+    $raw = reset($all);
+
+    // Put the current link onto the front.
+    array_unshift($parents, $raw['id']);
+
+    $query = $this->connection->select('menu_tree');
+    $query->fields('menu_tree', array('id', 'mlid'));
+    $query->condition('id', $parents, 'IN');
+    $found_parents = $query->execute()->fetchAllKeyed(0, 1);
+
+    $this->assertEqual(count($parents), count($found_parents), 'Found expected number of parents');
+    $this->assertEqual($raw['depth'], count($found_parents), 'Number of parents is the same as the depth');
+
+    $materialized_path = $this->treeStorage->getMaterializedPathIds($id);
+    $this->assertEqual(array_values($materialized_path), array_reverse($parents), 'Parents match the materialized path');
+    // Check that the selected mlid values of the parents are in the correct
+    // column, including the link's own.
+    for ($i = $raw['depth']; $i >= 1; $i--) {
+      $parent_id = array_shift($parents);
+      $this->assertEqual($raw["p$i"], $found_parents[$parent_id], "mlid of parent matches at column p$i");
+    }
+    for ($i = $raw['depth'] + 1; $i <= $this->treeStorage->maxDepth(); $i++) {
+      $this->assertEqual($raw["p$i"], 0, "parent is 0 at column p$i greater than depth");
+    }
+    if ($parents) {
+      $this->assertEqual($raw['parent'], end($parents), 'Ensure that the parent field is set properly');
+    }
+    $found_children = array_keys($this->treeStorage->loadAllChildLinks($id));
+    // We need both these checks since the 2nd will pass if there are extra
+    // IDs loaded in $found_children.
+    $this->assertEqual(count($children), count($found_children), "Found expected number of children for $id");
+    $this->assertEqual(array_intersect($children, $found_children), $children, 'Child IDs match');
+  }
+
+}
+
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/AdminTest.php b/core/modules/system/lib/Drupal/system/Tests/System/AdminTest.php
index 63b96f7..17371cf 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/AdminTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/AdminTest.php
@@ -54,9 +54,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,
@@ -111,25 +112,13 @@ 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();
-
-    $menu_items = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($menu_link_ids);
-    foreach ($menu_items as &$menu_item) {
-      _menu_link_translate($menu_item);
-    }
+    /** @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.
+    $menu_items = $menu_tree->getChildLinks('system.admin', 1);
     return $menu_items;
   }
 
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 4447052..0fbb7df 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -432,7 +432,7 @@ function hook_page_build(&$page) {
  *   - options: (optional) An array of options to be passed to l() when
  *     generating a link from this menu item.
  */
-function hook_menu_link_defaults_alter(&$links) {
+function hook_menu_links_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 8feb47b..7d942c2 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1444,74 +1444,41 @@ function system_admin_compact_mode() {
 function system_get_module_admin_tasks($module, array $info) {
   $links = &drupal_static(__FUNCTION__);
 
+  /* @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('plugin.manager.menu.link_tree');
+
   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;
-      }
-    }
+    /* @var \Drupal\Core\Menu\MenuLinkInterface $admin_link */
+    $links = $menu_tree->getChildLinks('system.admin');
   }
 
   $admin_tasks = array();
   $titles = array();
-  foreach ($links as $item) {
-    if ($item['module'] != $module) {
+  foreach ($links as $id => $instance) {
+    if ($instance->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[$id] = array(
+      'title' => $instance->getTitle(),
+      'description' => $instance->getDescription(),
+      'url' => $instance->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/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module
index a4212bc..29c53ad 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.module
+++ b/core/modules/system/tests/modules/menu_test/menu_test.module
@@ -8,13 +8,13 @@
 use Drupal\menu_link\Entity\MenuLink;
 
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_alter().
  *
  * 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.
  */
-function menu_test_menu_link_defaults_alter(&$links) {
+function menu_test_menu_links_alter(&$links) {
   $links['menu_test.menu_name_test']['menu_name'] = menu_test_menu_name();
   $links['menu_test.context']['title'] = \Drupal::config('menu_test.menu_item')->get('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/lib/Drupal/toolbar/Tests/ToolbarAdminMenuTest.php b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarAdminMenuTest.php
index 74543ee..80bb862 100644
--- a/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarAdminMenuTest.php
+++ b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarAdminMenuTest.php
@@ -133,27 +133,14 @@ function testModuleStatusChangeSubtreesHashCacheClear() {
    * Tests toolbar_menu_link_update() hook 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 = \Drupal::menuTree()->getChildLinks('system.admin', 2);
     $links = array_values($links);
     $link = array_shift($links);
 
     // 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/item/" . $link->getPluginId() . "/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 3599c59..69c1d9c 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -11,8 +11,6 @@
 use Drupal\Core\Template\Attribute;
 use Drupal\Component\Utility\Crypt;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Drupal\menu_link\MenuLinkInterface;
 use Drupal\user\RoleInterface;
 use Drupal\user\UserInterface;
 
@@ -323,8 +321,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',
@@ -349,14 +345,15 @@ function toolbar_toolbar() {
     '#weight' => -20,
   );
 
-  // Retrieve the administration menu from the database.
-  $tree = toolbar_get_menu_tree();
+  $menu_tree = \Drupal::menuTree();
+  // Retrieve the administration menu from the menu tree manager.
+  $tree = $menu_tree->buildTree('admin', array(
+    'expanded' => array('system.admin')
+  ));
 
+  $render_tree = $menu_tree->buildRenderTree($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');
+  toolbar_menu_navigation_links($render_tree);
 
   $menu = array(
     '#heading' => t('Administration menu'),
@@ -365,7 +362,7 @@ function toolbar_toolbar() {
       '#attributes' => array(
         'class' => array('toolbar-menu-administration'),
       ),
-      'administration_menu' => $menu_tree->renderTree($tree),
+      'administration_menu' => $render_tree,
     ),
   );
 
@@ -410,57 +407,44 @@ function toolbar_toolbar() {
 }
 
 /**
- * Gets only the top level items below the 'admin' path.
+ * Modifies a render array of links from a menu tree array.
  *
- * @return
- *   An array containing a menu tree of top level items below the 'admin' path.
- */
-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;
-}
-
-/**
- * Generates an array of links from a menu tree array.
+ * Adds route-based IDs and icon placeholders to the link attributes.
  *
- * Based on menu_navigation_links(). Adds path based IDs and icon placeholders
- * to the links.
- *
- * @return
- *   An array of links as defined above.
+ * @param array $render_tree
+ *   A menu link tree render array. The contained url objects are modified.
  */
-function toolbar_menu_navigation_links(&$tree) {
-  foreach ($tree as $key => $item) {
+function toolbar_menu_navigation_links($render_tree) {
+  foreach (Element::children($tree, FALSE) as $key) {
+    $element = $tree[$key];
     // Configure sub-items.
-    if (!empty($item['below'])) {
-      toolbar_menu_navigation_links($tree[$key]['below']);
+    if (!empty($element['#below'])) {
+      toolbar_menu_navigation_links($tree[$key]['#below']);
     }
+    /** @var \Drupal\Core\Url $url */
+    $url = $element['#url'];
     // 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']),
+    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());
+    }
+    $options = $url->getOptions();
+    $options['attributes'] = array(
+      'id' => 'toolbar-link-' . $id,
       'class' => array(
         'toolbar-icon',
-        'toolbar-icon-' . strtolower(str_replace(' ', '-', $item['link']['link_title'])),
       ),
-      'title' => check_plain($item['link']['description']),
     );
+    // Get the non-localized title to make the icon class.
+    $definition = $element['#original_link']->getPluginDefinition();
+    if (!empty($definition['title'])) {
+      $options['attributes']['class'][] = 'toolbar-icon-' . strtolower(str_replace(' ', '-', $definition['title']));
+    }
+    $url->setOptions($options);
   }
 }
 
@@ -468,32 +452,25 @@ function toolbar_menu_navigation_links(&$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('plugin.manager.menu.link_tree');
+  $top_tree = $menu_tree->buildSubtree('system.admin', 3);
+  $tree = !empty($top_tree['below']) ? $top_tree['below'] : array();
   $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) {
+    /** @var \Drupal\Core\Menu\MenuLinkInterface $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;
+    if ($tree_item['below']) {
+      $subtree = $menu_tree->buildRenderTree($tree_item['below']);debug(array_keys($subtree));
+      toolbar_menu_navigation_links($subtree);
+      $output = drupal_render($subtree);
     }
+    else {
+      $output = '';
+    }
+    $id = str_replace(array('.', '<', '>'), array('-', '', '' ), $item->getUrlObject()->getRouteName());
+
+    $subtrees[$id] = $output;
   }
   return $subtrees;
 }
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Menu/MyAccountMenuLink.php b/core/modules/user/lib/Drupal/user/Plugin/Menu/MyAccountMenuLink.php
new file mode 100644
index 0000000..31b40dd
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Menu/MyAccountMenuLink.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Menu\MyAccountMenuLink.
+ */
+
+namespace Drupal\user\Plugin\Menu;
+
+use Drupal\Core\Menu\MenuLinkDefault;
+
+/**
+ * Provides custom logic for the user.page menu link.
+ */
+class MyAccountMenuLink extends MenuLinkDefault {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isHidden() {
+    // The path 'user' must be accessible for anonymous users, but only visible
+    // for authenticated users. Authenticated users should see "My account", but
+    // anonymous users should not see it at all.
+    return (bool) \Drupal::currentUser()->isAnonymous();
+  }
+
+}
+
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php b/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php
index ce936e4..b187fa8 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php
@@ -67,13 +67,13 @@ function testSecondaryMenu() {
     $this->drupalGet('<front>');
 
     // For a logged-out user, expect no secondary links.
-    /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
-    $menu_tree = \Drupal::service('menu_link.tree');
+    /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+    $menu_tree = \Drupal::service('menu.link_tree');
     $tree = $menu_tree->buildTree('account');
     $this->assertEqual(count($tree), 1, 'The secondary links menu contains only one menu link.');
     $link = reset($tree);
     $link = $link['link'];
-    $this->assertTrue((bool) $link->hidden, 'The menu link is hidden.');
+    $this->assertTrue($link->isHidden(), 'The menu link is hidden.');
   }
 
   /**
diff --git a/core/modules/user/user.menu_links.yml b/core/modules/user/user.menu_links.yml
index 3455485..e63e5c7 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 955f644..fc25333 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -698,19 +698,6 @@ function theme_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) {
@@ -722,16 +709,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/lib/Drupal/views/Plugin/Derivative/ViewsMenuLink.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsMenuLink.php
new file mode 100644
index 0000000..55fbbc3
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsMenuLink.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Plugin\Derivative\ViewsMenuLink.
+ */
+
+namespace Drupal\views\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+use Drupal\views\Views;
+
+/**
+ * Provides menu links for views.
+ */
+class ViewsMenuLink implements DerivativeInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+    if (!isset($this->derivatives)) {
+      $this->getDerivativeDefinitions($base_plugin_definition);
+    }
+    if (isset($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    // @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/lib/Drupal/views/Plugin/Menu/Form/ViewsMenuLinkForm.php b/core/modules/views/lib/Drupal/views/Plugin/Menu/Form/ViewsMenuLinkForm.php
new file mode 100644
index 0000000..da8bef9
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/Plugin/Menu/Form/ViewsMenuLinkForm.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Plugin\Menu\Form\ViewsMenuLinkForm.
+ */
+
+namespace Drupal\views\Plugin\Menu\Form;
+
+use Drupal\Core\Menu\Form\MenuLinkDefaultForm;
+
+class ViewsMenuLinkForm extends MenuLinkDefaultForm {
+
+  public function buildEditForm(array &$form, array &$form_state) {
+    $form = parent::buildEditForm($form, $form_state);
+
+    $form['title'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Title'),
+      '#default_value' => $this->menuLink->getTitle(),
+    );
+
+    return $form;
+  }
+
+  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/lib/Drupal/views/Plugin/Menu/ViewsMenuLink.php b/core/modules/views/lib/Drupal/views/Plugin/Menu/ViewsMenuLink.php
new file mode 100644
index 0000000..85abc08
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/Plugin/Menu/ViewsMenuLink.php
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Plugin\Menu\ViewsMenuLink.
+ */
+
+namespace Drupal\views\Plugin\Menu;
+
+use Drupal\Core\Menu\MenuLinkBase;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\views\ViewExecutableFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class ViewsMenuLink extends MenuLinkBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var array
+   */
+  protected $overrideAllowed = array(
+    'menu_name' => 1,
+    'parent' => 1,
+    'weight' => 1,
+    'expanded' => 1,
+    'hidden' => 1,
+    'title' => 1,
+    'description' => 1,
+    'metadata' => 1,
+  );
+
+  /**
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * @var \Drupal\views\ViewExecutableFactory
+   */
+  protected $viewExecutableFactory;
+
+  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
+   */
+  protected 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'] = $form_state['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 getBasePluginId() {
+    $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/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
index 750546f..2f5a79a 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
@@ -2117,15 +2117,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/lib/Drupal/views/Plugin/views/display/PathPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php
index 44127ec..c3a3b97 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/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/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
index a7d4418..40370e3 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
@@ -31,7 +31,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,7 +56,6 @@ 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');
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Tests/Wizard/MenuTest.php b/core/modules/views/lib/Drupal/views/Tests/Wizard/MenuTest.php
index f294a89..84c6396 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Wizard/MenuTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Wizard/MenuTest.php
@@ -45,15 +45,8 @@ 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]'])));
+    $link = \Drupal::menuTree()->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/lib/Drupal/views/ViewExecutable.php b/core/modules/views/lib/Drupal/views/ViewExecutable.php
index 5eb82a6..4ed7a27 100644
--- a/core/modules/views/lib/Drupal/views/ViewExecutable.php
+++ b/core/modules/views/lib/Drupal/views/ViewExecutable.php
@@ -1516,13 +1516,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)) {
@@ -1531,7 +1530,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..883ee35
--- /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
+  derivative: \Drupal\views\Plugin\Derivative\ViewsMenuLink
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 216bc93..b4545b3 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 3f8116b..2db5a55 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
   - custom_block
   - quickedit
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index 73ea074..11390b8 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -38,16 +38,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' => '<front>',
-    'menu_name' => 'main',
-  ));
-  $menu_link->save();
-
   // Enable the Contact link in the footer menu.
-  menu_link_maintain('contact', 'enable', 'contact');
+  /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu.link_tree');
+  $menu_tree->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: '<front>'
+  menu_name: main
diff --git a/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeTest.php b/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeTest.php
new file mode 100644
index 0000000..cec56e8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeTest.php
@@ -0,0 +1,272 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Menu\MenuLinkTreeTest.
+ */
+
+namespace Drupal\Tests\Core\Menu;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Menu\MenuLinkDefault;
+use Drupal\Core\Menu\MenuLinkTree;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Tests the menu link tree.
+ *
+ * @group Drupal
+ * @group Menu
+ */
+class MenuLinkTreeTest extends UnitTestCase {
+
+  /**
+   * The tested menu link tree.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTree
+   */
+  protected $menuTree;
+
+  /**
+   * @var \Drupal\Core\Menu\MenuLinkTreeStorageInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $treeStorage;
+
+  protected $treeItemDefault = array(
+    'definition' => array(),
+    'has_children' => 0,
+    'in_active_trail' => TRUE,
+    'below' => array(),
+    'depth' => 1,
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Tests \Drupal\Core\Menu\MenuLinkTree',
+      'description' => '',
+      'group' => 'Menu'
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->treeStorage = $this->getMock('Drupal\Core\Menu\MenuLinkTreeStorageInterface');
+    $this->staticOverride = $this->getMock('Drupal\Core\Menu\StaticMenuLinkOverridesInterface');
+    $this->requestStack = new RequestStack();
+    $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->treeCacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
+    $this->accessManager = $this->getMockBuilder('Drupal\Core\Access\AccessManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $this->factory = $this->getMock('Drupal\Component\Plugin\Factory\FactoryInterface');
+    $this->menuTree = new TestMenuLinkTree($this->treeStorage, $this->staticOverride, $this->requestStack, $this->routeProvider, $this->moduleHandler, $this->treeCacheBackend, $this->languageManager, $this->accessManager, $this->account, $this->entityManager);
+    $this->stringTranslation = $this->getStringTranslationStub();
+    $container = new ContainerBuilder();
+    $container->set('string_translation', $this->stringTranslation);
+    \Drupal::setContainer($container);
+    $this->menuTree->setFactory($this->factory);
+  }
+
+  public function testMenuLinkGetPreferred() {
+    $this->menuLinkGetPreferredTreeStorageHelper();
+
+    $result = $this->menuTree->menuLinkGetPreferred('test_route');
+    $this->assertEquals('test1', $result->getPluginId());
+  }
+
+  public function testMenuLinkGetPreferredWithSpecifiedMenu() {
+    $this->menuLinkGetPreferredTreeStorageHelper();
+
+    $result = $this->menuTree->menuLinkGetPreferred('test_route', array(), 'tools');
+    $this->assertEquals('test2', $result->getPluginId());
+  }
+
+  public function testMenuLinkGetPreferredStaticCaching() {
+    // the helper uses $this->once() so we test the static caching with that.
+    $this->menuLinkGetPreferredTreeStorageHelper();
+
+    $result = $this->menuTree->menuLinkGetPreferred('test_route', array());
+    $this->assertEquals('test1', $result->getPluginId());
+    $result = $this->menuTree->menuLinkGetPreferred('test_route', array());
+    $this->assertEquals('test1', $result->getPluginId());
+  }
+
+  public function testMenuLinkGetPreferredStaticCachingWithSelectedMenu() {
+    $this->menuLinkGetPreferredTreeStorageHelper();
+
+    $result = $this->menuTree->menuLinkGetPreferred('test_route', array());
+    $this->assertEquals('test1', $result->getPluginId());
+
+    $result = $this->menuTree->menuLinkGetPreferred('test_route', array(), 'tools');
+    $this->assertEquals('test2', $result->getPluginId());
+  }
+
+  public function testMenuLinkGetPreferredWithNoMatchingMenuLink() {
+    $this->treeStorage->expects($this->once())
+      ->method('loadByRoute')
+      ->with('test_route')
+      ->will($this->returnValue(array()));
+    $this->accessManager->expects($this->any())
+      ->method('checkNamedRoute')
+      ->will($this->returnValue(TRUE));
+
+    $result = $this->menuTree->menuLinkGetPreferred('test_route', array(), 'tools');
+    $this->assertNull($result);
+  }
+
+  public function testMenuLinkGetPreferredWithAccessDenied() {
+    $this->accessManager->expects($this->any())
+      ->method('checkNamedRoute')
+      ->will($this->returnValue(FALSE));
+    $result = $this->menuTree->menuLinkGetPreferred('test_route', array());
+    $this->assertNull($result);
+  }
+
+  public function testMenuLinkGetPreferredForCurrentRequest() {
+    $request = new Request();
+    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route');
+    $raw_variables = new ParameterBag();
+    $request->attributes->set('_raw_variables', $raw_variables);
+    $this->requestStack->push($request);
+
+  }
+
+  protected function menuLinkGetPreferredTreeStorageHelper() {
+    $definitions = array(
+      'test1' => array(
+        'id' => 'test1',
+        'menu_name' => 'admin',
+        'class' => 'Drupal\Core\Menu\MenuLinkDefault',
+      ),
+      'test2' => array(
+        'id' => 'test2',
+        'menu_name' => 'tools',
+        'class' => 'Drupal\Core\Menu\MenuLinkDefault',
+      ),
+    );
+    $this->treeStorage->expects($this->once())
+      ->method('loadByRoute')
+      ->with('test_route')
+      ->will($this->returnValue($definitions));
+
+    $this->accessManager->expects($this->any())
+      ->method('checkNamedRoute')
+      ->will($this->returnValue(TRUE));
+    $this->factory->expects($this->any())
+      ->method('createInstance')
+      ->will($this->returnCallback(function ($plugin_id) use ($definitions) {
+        return new MenuLinkDefault(array(), $plugin_id, $definitions[$plugin_id], $this->staticOverride);
+      }));
+  }
+
+    /**
+   * Tests the output with a single level.
+   *
+   * @covers ::buildRenderTree
+   */
+  public function testOutputWithSingleLevel() {
+    $tree = array(
+      'test1' => array(
+        'link' => $this->menuLinkInstanceHelper('test1'),
+      ) + $this->treeItemDefault,
+      'test2' => array(
+        'link' => $this->menuLinkInstanceHelper('test2'),
+      ) + $this->treeItemDefault,
+    );
+
+    $output = $this->menuTree->buildRenderTree($tree);
+
+    // Validate that the - in main-menu is changed into an underscore
+    print_r($output);
+    $this->assertEquals($output['test1']['#theme'], 'menu_link__tools', 'Hyphen is changed to an underscore on menu_link');
+    $this->assertEquals($output['test2']['#theme'], 'menu_link__tools', 'Hyphen is changed to an underscore on menu_link');
+    $this->assertEquals($output['#theme_wrappers'][0], 'menu_tree__tools', 'Hyphen is changed to an underscore on menu_tree wrapper');
+  }
+
+  /**
+   * Tests the output method with a complex example.
+   *
+   * @covers ::buildRenderTree
+   */
+  public function testOutputWithComplexData() {
+    $tree = array(
+      'test1'=> array(
+        'link' => $this->menuLinkInstanceHelper('test1', 'Item 1', 'test_a'),
+        'below' => array(
+          'test2' => array('link' => $this->menuLinkInstanceHelper('test2', 'Item 2', 'test_a_b'),
+            'below' => array(
+              'test3' => array('link' => $this->menuLinkInstanceHelper('test3', 'Item 3', 'test_a_b_c'),
+              ) + $this->treeItemDefault,
+              'test4' => array('link' => $this->menuLinkInstanceHelper('test4', 'Item 4', 'test_a_b_d'),
+              ) + $this->treeItemDefault,
+            )
+          ) + $this->treeItemDefault,
+        ),
+      ) + $this->treeItemDefault,
+      'test5' => array('link' => $this->menuLinkInstanceHelper('test5', 'Item 5', 'test_e')) + $this->treeItemDefault,
+    );
+
+    $output = $this->menuTree->buildRenderTree($tree);
+
+    // Looking for child items in the data
+    $this->assertEquals($output['test1']['#below']['test2']['#url']->getRouteName(), 'test_a_b', 'Checking the href on a child item');
+    $this->assertTrue(in_array('active-trail', $output['test1']['#below']['test2']['#attributes']['class']), 'Checking the active trail class');
+    $this->assertTrue(isset($output['test5']), 'Item is present');
+  }
+
+  protected function menuLinkInstanceHelper($id, $title = '', $route_name = '', $extra = array()) {
+    $defaults = array(
+      'menu_name' => 'tools',
+      'route_name' => '<front>',
+      'route_parameters' => array(),
+      'url' => '',
+      'title' => '',
+      'title_arguments' => array(),
+      'title_context' => '',
+      'description' => '',
+      'parent' => '',
+      'weight' => 0,
+      'options' => array(),
+      'expanded' => 0,
+      'hidden' => 0,
+      'discovered' => 0,
+      'provider' => '',
+      'metadata' => array(),
+      'class' => 'Drupal\Core\Menu\MenuLinkDefault',
+      'form_class' => 'Drupal\Core\Menu\Form\MenuLinkDefaultForm',
+      'id' => '',
+    );
+    $defaults['title'] = $title;
+    $defaults['route_name'] = $route_name;
+    $defaults['id'] = $id;
+    $extra += $defaults;
+    return new MenuLinkDefault(array(), $defaults['id'], $extra, $this->staticOverride);
+  }
+
+}
+
+class TestMenuLinkTree extends MenuLinkTree {
+
+  protected function menuGetActiveMenuNames() {
+    return array();
+  }
+
+  public function setFactory(FactoryInterface $factory) {
+    $this->factory = $factory;
+  }
+
+}
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index c3e7a32..c5589e8 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -171,12 +171,11 @@ function seven_admin_block_content($variables) {
     foreach ($content as $item) {
       $output .= '<li>';
       $content = '<span class="label">' . Xss::filterAdmin($item['title']) . '</span>';
-      $options = $item['localized_options'];
-      $options['html'] = TRUE;
       if (isset($item['description']) && !system_admin_compact_mode()) {
         $content .= '<div class="description">' . Xss::filterAdmin($item['description']) . '</div>';
       }
-      $output .= l($content, $item['link_path'], $options);
+      $item['url']->setOption('html', TRUE);
+      $output .= \Drupal::linkGenerator()->generateFromUrl($content, $item['url']);
       $output .= '</li>';
     }
     $output .= '</ul>';
