diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc
index f74c196..e2c5141 100644
--- a/core/modules/book/book.admin.inc
+++ b/core/modules/book/book.admin.inc
@@ -29,18 +29,18 @@ function theme_book_admin_table($variables) {
   $access = \Drupal::currentUser()->hasPermission('administer nodes');
   foreach (element_children($form) as $key) {
     $nid = $form[$key]['nid']['#value'];
-    $href = $form[$key]['href']['#value'];
+    $href = \Drupal::url('node.view', array('node' => $nid));
 
     // Add special classes to be used with tabledrag.js.
-    $form[$key]['plid']['#attributes']['class'] = array('book-plid');
-    $form[$key]['mlid']['#attributes']['class'] = array('book-mlid');
+    $form[$key]['pid']['#attributes']['class'] = array('book-pid');
+    $form[$key]['nid']['#attributes']['class'] = array('book-nid');
     $form[$key]['weight']['#attributes']['class'] = array('book-weight');
 
     $indentation = array('#theme' => 'indentation', '#size' => $form[$key]['depth']['#value'] - 2);
     $data = array(
       drupal_render($indentation) . drupal_render($form[$key]['title']),
       drupal_render($form[$key]['weight']),
-      drupal_render($form[$key]['plid']) . drupal_render($form[$key]['mlid']),
+      drupal_render($form[$key]['pid']) . drupal_render($form[$key]['nid']),
     );
     $links = array();
     $links['view'] = array(
@@ -84,9 +84,9 @@ function theme_book_admin_table($variables) {
       array(
         'action' => 'match',
         'relationship' => 'parent',
-        'group' => 'book-plid',
-        'subgroup' => 'book-plid',
-        'source' => 'book-mlid',
+        'group' => 'book-pid',
+        'subgroup' => 'book-pid',
+        'source' => 'book-nid',
         'hidden' => TRUE,
         'limit' => MENU_MAX_DEPTH - 2,
       ),
diff --git a/core/modules/book/book.install b/core/modules/book/book.install
index 0b540bd..1a31749 100644
--- a/core/modules/book/book.install
+++ b/core/modules/book/book.install
@@ -23,34 +23,114 @@ function book_schema() {
   $schema['book'] = array(
   'description' => 'Stores book outline information. Uniquely connects each node in the outline to a link in {menu_links}',
     'fields' => array(
-      'mlid' => array(
+      'nid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => "The book page's {menu_links}.mlid.",
+        'description' => "The book page's {node}.nid.",
       ),
-      'nid' => array(
+      'bid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => "The book page's {node}.nid.",
+        'description' => "The book ID is the {book}.nid of the top-level page.",
       ),
-      'bid' => array(
+      'pid' => array(
+        'description' => 'The parent ID (pid) is the id of the node above in the hierarchy, or zero if the node is at the top level in its menu.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'has_children' => array(
+        'description' => 'Flag indicating whether any nodes have this node as a parent (1 = children exist, 0 = no children).',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'weight' => array(
+        'description' => 'Weight among book entries in the same book at the same depth.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'depth' => array(
+        'description' => 'The depth relative to the top level. A link with pid == 0 will have depth == 1.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'p1' => array(
+        'description' => 'The first nid in the materialized path. If N = depth, then pN must equal the nid. If depth > 1 then p(N-1) must equal the pid. 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 nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p3' => array(
+        'description' => 'The third nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p4' => array(
+        'description' => 'The fourth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p5' => array(
+        'description' => 'The fifth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p6' => array(
+        'description' => 'The sixth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p7' => array(
+        'description' => 'The seventh nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p8' => array(
+        'description' => 'The eighth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p9' => array(
+        'description' => 'The ninth nid in the materialized path. See p1.',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => "The book ID is the {book}.nid of the top-level page.",
       ),
     ),
-    'primary key' => array('mlid'),
-    'unique keys' => array(
-      'nid' => array('nid'),
-    ),
+    'primary key' => array('nid'),
     'indexes' => array(
-      'bid' => array('bid'),
+      'book_parents' => array('bid', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
     ),
   );
 
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index b201234..9bee68f 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\BookManagerInterface;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\node\NodeInterface;
@@ -41,29 +42,6 @@ function book_help($path, $arg) {
 }
 
 /**
- * Implements hook_entity_bundle_info().
- */
-function book_entity_bundle_info() {
-  $bundles['menu_link']['book-toc'] = array(
-    'label' => t('Book'),
-    'translatable' => FALSE,
-  );
-  return $bundles;
-}
-
-/**
- * Implements hook_TYPE_load().
- */
-function book_menu_link_load($entities) {
-  foreach ($entities as $entity) {
-    // Change the bundle of menu links related to a book.
-    if (strpos($entity->menu_name, 'book-toc-') === 0) {
-      $entity->bundle = 'book-toc';
-    }
-  }
-}
-
-/**
  * Implements hook_theme().
  */
 function book_theme() {
@@ -72,6 +50,9 @@ function book_theme() {
       'variables' => array('book_link' => NULL),
       'template' => 'book-navigation',
     ),
+    'book_link' => array(
+      'render element' => 'element',
+    ),
     'book_export_html' => array(
       'variables' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL),
       'template' => 'book-export-html',
@@ -138,7 +119,7 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$
           $links['book_add_child'] = array(
             'title' => t('Add child page'),
             'href' => 'node/add/' . $child_type,
-            'query' => array('parent' => $node->book['mlid']),
+            'query' => array('parent' => $node->id()),
           );
         }
 
@@ -222,14 +203,15 @@ function book_form_node_form_alter(&$form, &$form_state, $form_id) {
   $account = \Drupal::currentUser();
   $access = $account->hasPermission('administer book outlines');
   if (!$access) {
-    if ($account->hasPermission('add content to books') && ((!empty($node->book['mlid']) && !$node->isNew()) || book_type_is_allowed($node->getType()))) {
+    if ($account->hasPermission('add content to books') && ((!empty($node->book['bid']) && !$node->isNew()) || book_type_is_allowed($node->getType()))) {
       // Already in the book hierarchy, or this node type is allowed.
       $access = TRUE;
     }
   }
 
   if ($access) {
-    $form = \Drupal::service('book.manager')->addFormElements($form, $form_state, $node, $account);
+    $collapsed = !($node->isNew() && !empty($node->book['pid']));
+    $form = \Drupal::service('book.manager')->addFormElements($form, $form_state, $node, $account, $collapsed);
     // Since the "Book" dropdown can't trigger a form submission when
     // JavaScript is disabled, add a submit button to do that. book.admin.css hides
     // this button when JavaScript is enabled.
@@ -283,7 +265,7 @@ function book_pick_book_nojs_submit($form, &$form_state) {
  *   The rendered parent page select element.
  */
 function book_form_update($form, $form_state) {
-  return $form['book']['plid'];
+  return $form['book']['pid'];
 }
 
 /**
@@ -302,14 +284,14 @@ function book_form_update($form, $form_state) {
 function book_get_flat_menu($book_link) {
   $flat = &drupal_static(__FUNCTION__, array());
 
-  if (!isset($flat[$book_link['mlid']])) {
-    // Call bookTreeAllData() to take advantage of the menu system's caching.
-    $tree = \Drupal::service('book.manager')->bookTreeAllData($book_link['menu_name'], $book_link, $book_link['depth'] + 1);
-    $flat[$book_link['mlid']] = array();
-    _book_flatten_menu($tree, $flat[$book_link['mlid']]);
+  if (!isset($flat[$book_link['nid']])) {
+    // Call bookTreeAllData() to take advantage of caching.
+    $tree = \Drupal::service('book.manager')->bookTreeAllData($book_link['bid'], $book_link, $book_link['depth'] + 1);
+    $flat[$book_link['nid']] = array();
+    _book_flatten_menu($tree, $flat[$book_link['nid']]);
   }
 
-  return $flat[$book_link['mlid']];
+  return $flat[$book_link['nid']];
 }
 
 /**
@@ -324,11 +306,9 @@ function book_get_flat_menu($book_link) {
  */
 function _book_flatten_menu($tree, &$flat) {
   foreach ($tree as $data) {
-    if (!$data['link']['hidden']) {
-      $flat[$data['link']['mlid']] = $data['link'];
-      if ($data['below']) {
-        _book_flatten_menu($data['below'], $flat);
-      }
+    $flat[$data['link']['nid']] = $data['link'];
+    if ($data['below']) {
+      _book_flatten_menu($data['below'], $flat);
     }
   }
 }
@@ -345,31 +325,34 @@ function _book_flatten_menu($tree, &$flat) {
  */
 function book_prev($book_link) {
   // If the parent is zero, we are at the start of a book.
-  if ($book_link['plid'] == 0) {
+  if ($book_link['pid'] == 0) {
     return NULL;
   }
-  $flat = book_get_flat_menu($book_link);
   // Assigning the array to $flat resets the array pointer for use with each().
+  $flat = book_get_flat_menu($book_link);
   $curr = NULL;
   do {
     $prev = $curr;
     list($key, $curr) = each($flat);
-  } while ($key && $key != $book_link['mlid']);
+  } while ($key && $key != $book_link['nid']);
 
-  if ($key == $book_link['mlid']) {
+  if ($key == $book_link['nid']) {
+    /** @var BookManagerInterface $book_manager */
+    $book_manager = \Drupal::service('book.manager');
     // The previous page in the book may be a child of the previous visible link.
-    if ($prev['depth'] == $book_link['depth'] && $prev['has_children']) {
+    if ($prev['depth'] == $book_link['depth']) {
       // The subtree will have only one link at the top level - get its data.
-      $tree = \Drupal::service('book.manager')->bookMenuSubtreeData($prev);
+      $tree = $book_manager->bookMenuSubtreeData($prev);
       $data = array_shift($tree);
       // The link of interest is the last child - iterate to find the deepest one.
       while ($data['below']) {
         $data = end($data['below']);
       }
-
+      $book_manager->bookLinkTranslate($data['link']);
       return $data['link'];
     }
     else {
+      $book_manager->bookLinkTranslate($prev);
       return $prev;
     }
   }
@@ -382,19 +365,23 @@ function book_prev($book_link) {
  *   A fully loaded menu link that is part of the book hierarchy.
  *
  * @return
- *   A fully loaded menu link for the page after the one represented in
+ *   A fully loaded book link for the page after the one represented in
  *   $book_link.
  */
 function book_next($book_link) {
-  $flat = book_get_flat_menu($book_link);
   // Assigning the array to $flat resets the array pointer for use with each().
+  $flat = book_get_flat_menu($book_link);
   do {
     list($key, ) = each($flat);
   }
-  while ($key && $key != $book_link['mlid']);
+  while ($key && $key != $book_link['nid']);
 
-  if ($key == $book_link['mlid']) {
-    return current($flat);
+  if ($key == $book_link['nid']) {
+    $next = current($flat);
+    if ($next) {
+      \Drupal::service('book.manager')->bookLinkTranslate($next);
+    }
+    return $next;
   }
 }
 
@@ -417,9 +404,9 @@ function book_children($book_link) {
     do {
       $link = array_shift($flat);
     }
-    while ($link && ($link['mlid'] != $book_link['mlid']));
+    while ($link && ($link['nid'] != $book_link['nid']));
     // Continue though the array and collect the links whose parent is this page.
-    while (($link = array_shift($flat)) && $link['plid'] == $book_link['mlid']) {
+    while (($link = array_shift($flat)) && $link['pid'] == $book_link['nid']) {
       $data['link'] = $link;
       $data['below'] = '';
       $children[] = $data;
@@ -437,12 +424,11 @@ function book_children($book_link) {
  * Implements hook_node_load().
  */
 function book_node_load($nodes) {
-  $result = db_query("SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid IN (:nids)", array(':nids' =>  array_keys($nodes)), array('fetch' => PDO::FETCH_ASSOC));
+  $result = db_query("SELECT * FROM {book} WHERE nid IN (:nids)", array(':nids' =>  array_keys($nodes)), array('fetch' => PDO::FETCH_ASSOC));
   foreach ($result as $record) {
     $nodes[$record['nid']]->book = $record;
-    $nodes[$record['nid']]->book['href'] = $record['link_path'];
-    $nodes[$record['nid']]->book['title'] = $record['link_title'];
-    $nodes[$record['nid']]->book['options'] = unserialize($record['options']);
+    $nodes[$record['nid']]->book['link_path'] = 'node/' . $record['nid'];
+    $nodes[$record['nid']]->book['link_title'] = $nodes[$record['nid']]->label();
   }
 }
 
@@ -476,7 +462,7 @@ function book_node_presave(EntityInterface $node) {
   }
   // Make sure a new node gets a new menu link.
   if ($node->isNew()) {
-    $node->book['mlid'] = NULL;
+    $node->book['nid'] = NULL;
   }
 }
 
@@ -490,8 +476,6 @@ function book_node_insert(EntityInterface $node) {
       // New nodes that are their own book.
       $node->book['bid'] = $node->id();
     }
-    $node->book['nid'] = $node->id();
-    $node->book['menu_name'] = $book_manager->createMenuName($node->book['bid']);
     $book_manager->updateOutline($node);
   }
 }
@@ -506,8 +490,6 @@ function book_node_update(EntityInterface $node) {
       // New nodes that are their own book.
       $node->book['bid'] = $node->id();
     }
-    $node->book['nid'] = $node->id();
-    $node->book['menu_name'] = $book_manager->createMenuName($node->book['bid']);
     $book_manager->updateOutline($node);
   }
 }
@@ -517,23 +499,9 @@ function book_node_update(EntityInterface $node) {
  */
 function book_node_predelete(EntityInterface $node) {
   if (!empty($node->book['bid'])) {
-    if ($node->id() == $node->book['bid']) {
-      // Handle deletion of a top-level post.
-      $result = db_query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = :plid", array(
-        ':plid' => $node->book['mlid']
-      ));
-      foreach ($result as $child) {
-        $child_node = node_load($child->id());
-        $child_node->book['bid'] = $child_node->id();
-        \Drupal::service('book.manager')->updateOutline($child_node);
-      }
-    }
-    // @todo - remove this call when we change the schema.
-    menu_link_delete($node->book['mlid']);
-    db_delete('book')
-      ->condition('mlid', $node->book['mlid'])
-      ->execute();
-    drupal_static_reset('book_get_books');
+    /** @var BookManagerInterface $book_manager */
+    $book_manager = \Drupal::service('book.manager');
+    $book_manager->deleteFromBook($node->book['nid']);
   }
 }
 
@@ -552,12 +520,11 @@ function book_node_prepare_form(NodeInterface $node, $form_display, $operation,
     $query = \Drupal::request()->query;
     if ($node->isNew() && !is_null($query->get('parent')) && is_numeric($query->get('parent'))) {
       // Handle "Add child page" links:
-      $parent = book_link_load($query->get('parent'));
+      $parent = $book_manager->loadBookLink($query->get('parent'), TRUE);
 
       if ($parent && $parent['access']) {
         $node->book['bid'] = $parent['bid'];
-        $node->book['plid'] = $parent['mlid'];
-        $node->book['menu_name'] = $parent['menu_name'];
+        $node->book['pid'] = $parent['nid'];
       }
     }
     // Set defaults.
@@ -633,7 +600,7 @@ function template_preprocess_book_all_books_block(&$variables) {
  * @param array $variables
  *   An associative array containing the following key:
  *   - book_link: An associative array of book link properties.
- *     Properties used: bid, link_title, depth, plid, mlid.
+ *     Properties used: bid, link_title, depth, pid, nid.
  */
 function template_preprocess_book_navigation(&$variables) {
   $book_link = $variables['book_link'];
@@ -641,17 +608,17 @@ function template_preprocess_book_navigation(&$variables) {
   // Provide extra variables for themers. Not needed by default.
   $variables['book_id'] = $book_link['bid'];
   $variables['book_title'] = String::checkPlain($book_link['link_title']);
-  $variables['book_url'] = 'node/' . $book_link['bid'];
+  $variables['book_url'] = \Drupal::url('node.view', array('node' => $book_link['bid']));
   $variables['current_depth'] = $book_link['depth'];
   $variables['tree'] = '';
 
-  if ($book_link['mlid']) {
+  if ($book_link['nid']) {
     $variables['tree'] = book_children($book_link);
 
     $build = array();
 
     if ($prev = book_prev($book_link)) {
-      $prev_href = url($prev['link_path']);
+      $prev_href = \Drupal::url('node.view', array('node' => $prev['nid']));
       $build['#attached']['drupal_add_html_head_link'][][] = array(
         'rel' => 'prev',
         'href' => $prev_href,
@@ -660,8 +627,10 @@ function template_preprocess_book_navigation(&$variables) {
       $variables['prev_title'] = String::checkPlain($prev['title']);
     }
 
-    if ($book_link['plid'] && $parent = book_link_load($book_link['plid'])) {
-      $parent_href = url($parent['link_path']);
+    /** @var BookManagerInterface $book_manager */
+    $book_manager = \Drupal::service('book.manager');
+    if ($book_link['pid'] && $parent = $book_manager->loadBookLink($book_link['pid'])) {
+      $parent_href = \Drupal::url('node.view', array('node' => $book_link['pid']));
       $build['#attached']['drupal_add_html_head_link'][][] = array(
         'rel' => 'up',
         'href' => $parent_href,
@@ -671,7 +640,7 @@ function template_preprocess_book_navigation(&$variables) {
     }
 
     if ($next = book_next($book_link)) {
-      $next_href = url($next['link_path']);
+      $next_href = \Drupal::url('node.view', array('node' => $next['nid']));
       $build['#attached']['drupal_add_html_head_link'][][] = array(
         'rel' => 'next',
         'href' => $next_href,
@@ -746,6 +715,27 @@ function template_preprocess_book_node_export_html(&$variables) {
 }
 
 /**
+ * Returns HTML for a book link and subtree.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: Structured array data for a book link.
+ *
+ * @ingroup themeable
+ */
+function theme_book_link(array $variables) {
+  $element = $variables['element'];
+  $sub_menu = '';
+
+  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']);
+  return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
+}
+
+/**
  * Determines if a given node type is in the list of types allowed for books.
  *
  * @param string $type
@@ -788,19 +778,21 @@ function book_node_type_update(NodeTypeInterface $type) {
 }
 
 /**
- * Gets a book menu link by its menu link ID.
- *
- * Like menu_link_load(), but adds additional data from the {book} table.
- *
- * Do not call when loading a node, since this function may call node_load().
- *
- * @param $mlid
- *   The menu link ID of the menu item.
- *
- * @return
- *   A menu link, with the link translated for rendering and data added from the
- *   {book} table. FALSE if there is an error.
+ * Implements hook_library_info().
  */
-function book_link_load($mlid) {
-  return entity_load('menu_link', $mlid);
+function book_library_info() {
+  $libraries['drupal.book'] = array(
+    'title' => 'Book',
+    'version' => \Drupal::VERSION,
+    'js' => array(
+      drupal_get_path('module', 'book') . '/book.js' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'drupal'),
+      array('system', 'drupal.form'),
+    ),
+  );
+
+  return $libraries;
 }
diff --git a/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php b/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
index 7b12219..9477a76 100644
--- a/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
+++ b/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
@@ -8,6 +8,7 @@
 namespace Drupal\book\Access;
 
 use Drupal\book\BookManager;
+use Drupal\book\BookManagerInterface;
 use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
@@ -21,17 +22,17 @@ class BookNodeIsRemovableAccessCheck implements AccessInterface {
   /**
    * Book Manager Service.
    *
-   * @var \Drupal\book\BookManager
+   * @var BookManagerInterface
    */
   protected $bookManager;
 
   /**
    * Constructs a BookNodeIsRemovableAccessCheck object.
    *
-   * @param \Drupal\book\BookManager $book_manager
+   * @param BookManagerInterface $book_manager
    *   Book Manager Service.
    */
-  public function __construct(BookManager $book_manager) {
+  public function __construct(BookManagerInterface $book_manager) {
     $this->bookManager = $book_manager;
   }
 
diff --git a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
index 6dd54ff..0f16d1f 100644
--- a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
+++ b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
@@ -19,11 +19,11 @@
 class BookBreadcrumbBuilder extends BreadcrumbBuilderBase {
 
   /**
-   * The menu link storage controller.
+   * The node storage controller.
    *
-   * @var \Drupal\menu_link\MenuLinkStorageControllerInterface
+   * @var \Drupal\Core\Entity\EntityStorageControllerInterface
    */
-  protected $menuLinkStorage;
+  protected $nodeStorage;
 
   /**
    * The access manager.
@@ -50,7 +50,7 @@ class BookBreadcrumbBuilder extends BreadcrumbBuilderBase {
    *   The current user account.
    */
   public function __construct(EntityManagerInterface $entity_manager, AccessManager $access_manager, AccountInterface $account) {
-    $this->menuLinkStorage = $entity_manager->getStorageController('menu_link');
+    $this->nodeStorage = $entity_manager->getStorageController('node');
     $this->accessManager = $access_manager;
     $this->account = $account;
   }
@@ -68,22 +68,22 @@ public function applies(array $attributes) {
    * {@inheritdoc}
    */
   public function build(array $attributes) {
-    $mlids = array();
+    $book_nids = array();
     $links = array($this->l($this->t('Home'), '<front>'));
     $book = $attributes['node']->book;
     $depth = 1;
     // We skip the current node.
     while (!empty($book['p' . ($depth + 1)])) {
-      $mlids[] = $book['p' . $depth];
+      $book_nids[] = $book['p' . $depth];
       $depth++;
     }
-    $menu_links = $this->menuLinkStorage->loadMultiple($mlids);
-    if (count($menu_links) > 0) {
+    $parent_books = $this->nodeStorage->loadMultiple($book_nids);
+    if (count($parent_books) > 0) {
       $depth = 1;
       while (!empty($book['p' . ($depth + 1)])) {
-        if (!empty($menu_links[$book['p' . $depth]]) && ($menu_link = $menu_links[$book['p' . $depth]])) {
-          if ($this->accessManager->checkNamedRoute($menu_link->route_name, $menu_link->route_parameters, $this->account)) {
-            $links[] = $this->l($menu_link->label(), $menu_link->route_name, $menu_link->route_parameters, $menu_link->options);
+        if (!empty($parent_books[$book['p' . $depth]]) && ($parent_book = $parent_books[$book['p' . $depth]])) {
+          if ($parent_book->access('view', $this->account)) {
+            $links[] = $this->l($parent_book->label(), 'node.view', array('node' => $parent_book->id()));
           }
         }
         $depth++;
diff --git a/core/modules/book/lib/Drupal/book/BookManager.php b/core/modules/book/lib/Drupal/book/BookManager.php
index 8edd4d7..192bd51 100644
--- a/core/modules/book/lib/Drupal/book/BookManager.php
+++ b/core/modules/book/lib/Drupal/book/BookManager.php
@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Contains \Drupal\book\BookManager.
@@ -6,6 +7,7 @@
 
 namespace Drupal\book;
 
+use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\EntityInterface;
@@ -16,10 +18,12 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\node\NodeInterface;
 
-/**
- * Book Manager Service.
- */
-class BookManager {
+class BookManager implements BookManagerInterface {
+
+  /**
+   * Defines the maximum supported depth of the book tree.
+   */
+  const BOOK_MAX_DEPTH = 9;
 
   /**
    * Database Service Object.
@@ -62,18 +66,12 @@ class BookManager {
   public function __construct(Connection $connection, EntityManagerInterface $entity_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory) {
     $this->connection = $connection;
     $this->entityManager = $entity_manager;
-    $this->translation =  $translation;
+    $this->translation = $translation;
     $this->configFactory = $config_factory;
   }
 
   /**
-   * Returns an array of all books.
-   *
-   * This list may be used for generating a list of all the books, or for building
-   * the options for a form select.
-   *
-   * @return
-   *   An array of all books.
+   * {@inheritdoc}
    */
   public function getAllBooks() {
     if (!isset($this->books)) {
@@ -91,23 +89,20 @@ protected function loadBooks() {
 
     if ($nids) {
       $query = $this->connection->select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC));
-      $query->join('menu_links', 'ml', 'b.mlid = ml.mlid');
       $query->fields('b');
-      $query->fields('ml');
       $query->condition('b.nid', $nids);
-      $query->orderBy('ml.weight');
-      $query->orderBy('ml.link_title');
       $query->addTag('node_access');
       $query->addMetaData('base_table', 'book');
       $book_links = $query->execute();
 
       $nodes = $this->entityManager->getStorageController('node')->loadMultiple($nids);
+      // @todo: Sort by weight and translated title.
 
+      // @todo: use route name for links, not system path.
       foreach ($book_links as $link) {
         $nid = $link['nid'];
         if (isset($nodes[$nid]) && $nodes[$nid]->status) {
-          $link['href'] = $link['link_path'];
-          $link['options'] = unserialize($link['options']);
+          $link['link_path'] = 'node/' . $nid;
           $link['title'] = $nodes[$nid]->label();
           $link['type'] = $nodes[$nid]->bundle();
           $this->books[$link['bid']] = $link;
@@ -117,56 +112,40 @@ protected function loadBooks() {
   }
 
   /**
-   * Returns an array with default values for a book page's menu link.
-   *
-   * @param string|int $nid
-   *   The ID of the node whose menu link is being created.
-   *
-   * @return array
-   *   The default values for the menu link.
+   * {@inheritdoc}
    */
   public function getLinkDefaults($nid) {
     return array(
       'original_bid' => 0,
-      'menu_name' => '',
       'nid' => $nid,
       'bid' => 0,
-      'link_path' => 'node/%',
-      'plid' => 0,
-      'mlid' => 0,
+      'pid' => 0,
       'has_children' => 0,
       'weight' => 0,
-      'module' => 'book',
       'options' => array(),
     );
   }
 
   /**
-   * Finds the depth limit for items in the parent select.
-   *
-   * @param array $book_link
-   *   A fully loaded menu link that is part of the book hierarchy.
-   *
-   * @return int
-   *   The depth limit for items in the parent select.
+   * {@inheritdoc}
    */
   public function getParentDepthLimit(array $book_link) {
-    return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? $this->findChildrenRelativeDepth($book_link) : 0);
+    return static::BOOK_MAX_DEPTH - 1 - (($book_link['bid'] && $book_link['has_children']) ? $this->findChildrenRelativeDepth($book_link) : 0);
   }
 
   /**
    * {@inheritdoc}
    */
   protected function findChildrenRelativeDepth(array $entity) {
-    $query = db_select('menu_links');
-    $query->addField('menu_links', 'depth');
-    $query->condition('menu_name', $entity['menu_name']);
+    $query = db_select('book');
+    $query->addField('book', 'depth');
+    $query->condition('bid', $entity['bid']);
     $query->orderBy('depth', 'DESC');
     $query->range(0, 1);
 
     $i = 1;
     $p = 'p1';
-    while ($i <= MENU_MAX_DEPTH && $entity[$p]) {
+    while ($i <= static::BOOK_MAX_DEPTH && $entity[$p]) {
       $query->condition($p, $entity[$p]);
       $p = 'p' . ++$i;
     }
@@ -177,21 +156,9 @@ protected function findChildrenRelativeDepth(array $entity) {
   }
 
   /**
-   * Builds the common elements of the book form for the node and outline forms.
-   *
-   * @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.
-   * @param \Drupal\node\NodeInterface $node
-   *   The node whose form is being viewed.
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The account viewing the form.
-   *
-   * @return array
-   *   The form structure, with the book elements added.
+   * {@inheritdoc}
    */
-  public function addFormElements(array $form, array &$form_state, NodeInterface $node, AccountInterface $account) {
+  public function addFormElements(array $form, array &$form_state, NodeInterface $node, AccountInterface $account, $collapsed = TRUE) {
     // If the form is being processed during the Ajax callback of our book bid
     // dropdown, then $form_state will hold the value that was selected.
     if (isset($form_state['values']['book'])) {
@@ -201,6 +168,7 @@ public function addFormElements(array $form, array &$form_state, NodeInterface $
       '#type' => 'details',
       '#title' => $this->t('Book outline'),
       '#weight' => 10,
+      '#open' => !$collapsed,
       '#group' => 'advanced',
       '#attributes' => array(
         'class' => array('book-outline-form'),
@@ -210,14 +178,14 @@ public function addFormElements(array $form, array &$form_state, NodeInterface $
       ),
       '#tree' => TRUE,
     );
-    foreach (array('menu_name', 'mlid', 'nid', 'link_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) {
+    foreach (array('nid', 'has_children', 'original_bid', 'parent_depth_limit') as $key) {
       $form['book'][$key] = array(
         '#type' => 'value',
         '#value' => $node->book[$key],
       );
     }
 
-    $form['book']['plid'] = $this->addParentSelectFormElements($node->book);
+    $form['book']['pid'] = $this->addParentSelectFormElements($node->book);
 
     // @see \Drupal\book\Form\BookAdminEditForm::bookAdminTableTree(). The
     // weight may be larger than 15.
@@ -245,7 +213,7 @@ public function addFormElements(array $form, array &$form_state, NodeInterface $
       // The node can become a new book, if it is not one already.
       $options = array($nid => $this->t('- Create a new book -')) + $options;
     }
-    if (!$node->book['mlid']) {
+    if (!$node->book['bid']) {
       // The node is not currently in the hierarchy.
       $options = array(0 => $this->t('- None -')) + $options;
     }
@@ -271,93 +239,70 @@ public function addFormElements(array $form, array &$form_state, NodeInterface $
   }
 
   /**
-   * Determines if a node can be removed from the book.
-   *
-   * A node can be removed from a book if it is actually in a book and it either
-   * is not a top-level page or is a top-level page with no children.
-   *
-   * @param \Drupal\node\NodeInterface $node
-   *   The node to remove from the outline.
-   *
-   * @return bool
-   *   TRUE if a node can be removed from the book, FALSE otherwise.
+   * {@inheritdoc}
    */
   public function checkNodeIsRemovable(NodeInterface $node) {
     return (!empty($node->book['bid']) && (($node->book['bid'] != $node->id()) || !$node->book['has_children']));
   }
 
   /**
-   * Handles additions and updates to the book outline.
-   *
-   * This common helper function performs all additions and updates to the book
-   * outline through node addition, node editing, node deletion, or the outline
-   * tab.
-   *
-   * @param \Drupal\node\NodeInterface $node
-   *   The node that is being saved, added, deleted, or moved.
-   *
-   * @return bool
-   *   TRUE if the menu link was saved; FALSE otherwise.
+   * {@inheritdoc}
    */
   public function updateOutline(NodeInterface $node) {
     if (empty($node->book['bid'])) {
       return FALSE;
     }
-    $new = empty($node->book['mlid']);
+    // Ensure we create a new book link if either the node itself is new, or the
+    // bid was selected the first time, so that the original_bid is still empty.
+    $new = empty($node->book['nid']) || empty($node->book['original_bid']);
 
-    $node->book['link_path'] = 'node/' . $node->id();
-    $node->book['link_title'] = $node->label();
-    $node->book['parent_mismatch'] = FALSE; // The normal case.
+    $node->book['nid'] = $node->id();
 
+    // Create a new book from a node.
     if ($node->book['bid'] == $node->id()) {
-      $node->book['plid'] = 0;
-      $node->book['menu_name'] = $this->createMenuName($node->id());
+      $node->book['pid'] = 0;
     }
-    else {
-      // Check in case the parent is not is this book; the book takes precedence.
-      if (!empty($node->book['plid'])) {
-        $parent = $this->connection->query("SELECT * FROM {book} WHERE mlid = :mlid", array(
-          ':mlid' => $node->book['plid'],
-        ))->fetchAssoc();
-      }
-      if (empty($node->book['plid']) || !$parent || $parent['bid'] != $node->book['bid']) {
-        $node->book['plid'] = $this->connection->query("SELECT mlid FROM {book} WHERE nid = :nid", array(
-          ':nid' => $node->book['bid'],
-        ))->fetchField();
-        $node->book['parent_mismatch'] = TRUE; // Likely when JS is disabled.
-      }
+    elseif ($node->book['pid'] < 0) {
+      // -1 is the default value in BookManager::addParentSelectFormElements().
+      // The node save should have set the bid equal to the node ID, but
+      // handle it here if it did not.
+      $node->book['pid'] = $node->book['bid'];
     }
+    return $this->saveBookLink($node->book, $new);
+  }
 
-    $node->book = $this->entityManager
-      ->getStorageController('menu_link')->create($node->book);
-    if ($node->book->save()) {
-      if ($new) {
-        // Insert new.
-        $this->connection->insert('book')
-          ->fields(array(
-            'nid' => $node->id(),
-            'mlid' => $node->book['mlid'],
-            'bid' => $node->book['bid'],
-          ))
-          ->execute();
+  /**
+   * {@inheritdoc}
+   */
+  public function getBookParents(array $item, array $parent = array()) {
+    $book = array();
+    if ($item['pid'] == 0) {
+      $book['p1'] = $item['nid'];
+      for ($i = 2; $i <= static::BOOK_MAX_DEPTH; $i++) {
+        $parent_property = "p$i";
+        $book[$parent_property] = 0;
       }
-      else {
-        if ($node->book['bid'] != $this->connection->query("SELECT bid FROM {book} WHERE nid = :nid", array(
-          ':nid' => $node->id(),
-        ))->fetchField()) {
-          // Update the bid for this page and all children.
-          $this->updateId($node->book);
-        }
+      $book['depth'] = 1;
+    }
+    else {
+      $i = 1;
+      $book['depth'] = $parent['depth'] + 1;
+      while ($i < $book['depth']) {
+        $p = 'p' . $i++;
+        $book[$p] = $parent[$p];
+      }
+      $p = 'p' . $i++;
+      // The parent (p1 - p9) corresponding to the depth always equals the nid.
+      $book[$p] = $item['nid'];
+      while ($i <= static::BOOK_MAX_DEPTH) {
+        $p = 'p' . $i++;
+        $book[$p] = 0;
       }
-
-      return TRUE;
     }
-
-    // Failed to save the menu link.
-    return FALSE;
+    return $book;
   }
 
-/**
+  /**
    * Translates a string to the current language or to a given language.
    *
    * See the t() documentation for details.
@@ -367,41 +312,6 @@ protected function t($string, array $args = array(), array $options = array()) {
   }
 
   /**
-   * Generates the corresponding menu name from a book ID.
-   *
-   * @param $id
-   *   The book ID for which to make a menu name.
-   *
-   * @return
-   *   The menu name.
-   */
-  public function createMenuName($id) {
-    return 'book-toc-' . $id;
-  }
-
-  /**
-   * Updates the book ID of a page and its children when it moves to a new book.
-   *
-   * @param array $book_link
-   *   A fully loaded menu link that is part of the book hierarchy.
-   */
-  public function updateId($book_link) {
-    $query = $this->connection->select('menu_links');
-    $query->addField('menu_links', 'mlid');
-    for ($i = 1; $i <= MENU_MAX_DEPTH && $book_link["p$i"]; $i++) {
-      $query->condition("p$i", $book_link["p$i"]);
-    }
-    $mlids = $query->execute()->fetchCol();
-
-    if ($mlids) {
-      $this->connection->update('book')
-        ->fields(array('bid' => $book_link['bid']))
-        ->condition('mlid', $mlids, 'IN')
-        ->execute();
-    }
-  }
-
-  /**
    * Builds the parent selection form element for the node form or outline tab.
    *
    * This function is also called when generating a new set of options during the
@@ -415,7 +325,7 @@ public function updateId($book_link) {
    *   A parent selection form element.
    */
   protected function addParentSelectFormElements(array $book_link) {
-    if ($this->configFactory->get('menu.settings')->get('override_parent_selector')) {
+    if ($this->configFactory->get('book.settings')->get('override_parent_selector')) {
       return array();
     }
     // Offer a message or a drop-down to choose a different parent page.
@@ -442,9 +352,9 @@ protected function addParentSelectFormElements(array $book_link) {
       $form = array(
         '#type' => 'select',
         '#title' => $this->t('Parent item'),
-        '#default_value' => $book_link['plid'],
-        '#description' => $this->t('The parent page in the book. The maximum depth for a book and all child pages is !maxdepth. Some pages in the selected book may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
-        '#options' => $this->getTableOfContents($book_link['bid'], $book_link['parent_depth_limit'], array($book_link['mlid'])),
+        '#default_value' => $book_link['pid'],
+        '#description' => $this->t('The parent page in the book. The maximum depth for a book and all child pages is !maxdepth. Some pages in the selected book may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => static::BOOK_MAX_DEPTH)),
+        '#options' => $this->getTableOfContents($book_link['bid'], $book_link['parent_depth_limit'], array($book_link['nid'])),
         '#attributes' => array('class' => array('book-title-select')),
         '#prefix' => '<div id="edit-book-plid-wrapper">',
         '#suffix' => '</div>',
@@ -477,39 +387,36 @@ protected function addParentSelectFormElements(array $book_link) {
    *   Any link deeper than this value will be excluded (along with its children).
    */
   protected function recurseTableOfContents(array $tree, $indent, array &$toc, array $exclude, $depth_limit) {
+    $nids = array();
     foreach ($tree as $data) {
       if ($data['link']['depth'] > $depth_limit) {
         // Don't iterate through any links on this level.
         break;
       }
+      if (!in_array($data['link']['nid'], $exclude)) {
+        $nids[] = $data['link']['nid'];
+      }
+    }
 
-      if (!in_array($data['link']['mlid'], $exclude)) {
-        $toc[$data['link']['mlid']] = $indent . ' ' . truncate_utf8($data['link']['title'], 30, TRUE, TRUE);
-        if ($data['below']) {
-          $this->recurseTableOfContents($data['below'], $indent . '--', $toc, $exclude, $depth_limit);
-        }
+    $nodes = $this->entityManager->getStorageController('node')->loadMultiple($nids);
+
+    foreach ($tree as $data) {
+      $nid = $data['link']['nid'];
+      if (in_array($nid, $exclude)) {
+        continue;
+      }
+      $toc[$nid] = $indent . ' ' . Unicode::truncate($nodes[$nid]->label(), 30, TRUE, TRUE);
+      if ($data['below']) {
+        $this->recurseTableOfContents($data['below'], $indent . '--', $toc, $exclude, $depth_limit);
       }
     }
   }
 
   /**
-   * Returns an array of book pages in table of contents order.
-   *
-   * @param int $bid
-   *   The ID of the book whose pages are to be listed.
-   * @param int $depth_limit
-   *   Any link deeper than this value will be excluded (along with its children).
-   * @param array $exclude
-   *   (optional) An array of menu link ID values. Any link whose menu link ID is
-   *   in this array will be excluded (along with its children). Defaults to an
-   *   empty array.
-   *
-   * @return array
-   *   An array of (menu link ID, title) pairs for use as options for selecting a
-   *   book page.
+   * {@inheritdoc}
    */
   public function getTableOfContents($bid, $depth_limit, array $exclude = array()) {
-    $tree = $this->bookTreeAllData($this->createMenuName($bid));
+    $tree = $this->bookTreeAllData($bid);
     $toc = array();
     $this->recurseTableOfContents($tree, '', $toc, $exclude, $depth_limit);
 
@@ -517,104 +424,69 @@ public function getTableOfContents($bid, $depth_limit, array $exclude = array())
   }
 
   /**
-   * Deletes node's entry form book table.
-   *
-   * @param int $nid
-   *   The nid to delete.
+   * {@inheritdoc}
    */
-  public function deleteBook($nid) {
+  public function deleteFromBook($nid) {
+    $original = $this->loadBookLink($nid, FALSE);
     $this->connection->delete('book')
       ->condition('nid', $nid)
       ->execute();
+    if ($nid == $original['bid']) {
+      // Handle deletion of a top-level post.
+      $result = $this->connection->query("SELECT * FROM {book} WHERE pid = :nid", array(
+        ':nid' => $nid
+      ))->fetchAllAssoc('nid', \PDO::FETCH_ASSOC);
+      foreach ($result as $child) {
+        $child['bid'] = $child['nid'];
+        $this->updateOutline($child);
+      }
+    }
+    $this->updateOriginalParent($original);
+    $this->books = NULL;
+    \Drupal::cache('menu')->deleteTags(array('bid' => $original['bid']));
   }
 
   /**
-   * 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 $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.
-   *
-   * Note: copied from menu_tree_all_data().
+   * {@inheritdoc}
    */
-  public function bookTreeAllData($menu_name, $link = NULL, $max_depth = NULL) {
-    $tree = &drupal_static('menu_tree_all_data', array());
+  public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL) {
+    $tree = &drupal_static(__METHOD__, array());
     $language_interface = \Drupal::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;
+    // Use $nid as a flag for whether the data being loaded is for the whole tree.
+    $nid = isset($link['nid']) ? $link['nid'] : 0;
+    // Generate a cache ID (cid) specific for this $bid, $link, $language, and depth.
+    $cid = 'book-links:' . $bid . ':all:' . $nid . ':' . $language_interface->id . ':' . (int) $max_depth;
 
     if (!isset($tree[$cid])) {
-      // If the static variable doesn't have the data, check {cache_menu}.
-      $cache = \Drupal::cache('menu')->get($cid);
-      if ($cache && isset($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"];
-            }
+      // If the tree data was not in the static cache, build $tree_parameters.
+      $tree_parameters = array(
+        'min_depth' => 1,
+        'max_depth' => $max_depth,
+      );
+      if ($nid) {
+        // 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 < static::BOOK_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.
-        \Drupal::cache('menu')->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
+        $tree_parameters['expanded'] = $parents;
+        $tree_parameters['active_trail'] = $parents;
+        $tree_parameters['active_trail'][] = $nid;
       }
 
-      // Build the tree using the parameters; the resulting tree will be cached
-      // by _menu_build_tree()).
-      $tree[$cid] = $this->menu_build_tree($menu_name, $tree_parameters);
+      // Build the tree using the parameters; the resulting tree will be cached.
+      $tree[$cid] = $this->menu_build_tree($bid, $tree_parameters);
     }
 
     return $tree[$cid];
   }
 
   /**
-   * 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().
-   *
-   * @todo This was copied from menu_tree_output() but with some changes that
-   *   may be obsolete. Attempt to resolve the differences.
+   * {@inheritdoc}
    */
   public function bookTreeOutput(array $tree) {
     $build = array();
@@ -623,7 +495,7 @@ public function bookTreeOutput(array $tree) {
     // 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']) {
+      if ($data['link']['access']) {
         $items[] = $data;
       }
     }
@@ -656,22 +528,23 @@ public function bookTreeOutput(array $tree) {
       }
 
       // Allow menu-specific theme overrides.
-      $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
+      $element['#theme'] = 'book_link__book_toc_' . $data['link']['bid'];
       $element['#attributes']['class'] = $class;
       $element['#title'] = $data['link']['title'];
-      $element['#href'] = $data['link']['link_path'];
+      $node = \Drupal::entityManager()->getStorageController('node')->load($data['link']['nid']);
+      $element['#href'] = $node->url();
       $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
       $element['#below'] = $data['below'] ? $this->bookTreeOutput($data['below']) : $data['below'];
       $element['#original_link'] = $data['link'];
-      // Index using the link's unique mlid.
-      $build[$data['link']['mlid']] = $element;
+      // Index using the link's unique nid.
+      $build[$data['link']['nid']] = $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($data['link']['menu_name'], '-', '_');
+      $build['#theme_wrappers'][] = 'menu_tree__book_toc_' . $data['link']['nid'];
     }
 
     return $build;
@@ -680,8 +553,8 @@ public function bookTreeOutput(array $tree) {
   /**
    * Builds a menu tree, translates links, and checks access.
    *
-   * @param string $menu_name
-   *   The name of the menu.
+   * @param int $bid
+   *   The Book ID to find links for.
    * @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
@@ -701,11 +574,11 @@ public function bookTreeOutput(array $tree) {
    * @return array
    *   A fully built menu tree.
    */
-  protected function menu_build_tree($menu_name, array $parameters = array()) {
+  protected function menu_build_tree($bid, array $parameters = array()) {
     // Build the menu tree.
-    $data = $this->_menu_build_tree($menu_name, $parameters);
+    $data = $this->_menu_build_tree($bid, $parameters);
     // Check access for the current user to each item in the tree.
-    menu_tree_check_access($data['tree'], $data['node_links']);
+    $this->bookTreeCheckAccess($data['tree'], $data['node_links']);
     return $data['tree'];
   }
 
@@ -718,9 +591,9 @@ protected function menu_build_tree($menu_name, array $parameters = array()) {
    *
    * @see menu_build_tree()
    */
-  protected function _menu_build_tree($menu_name, array $parameters = array()) {
+  protected function _menu_build_tree($bid, array $parameters = array()) {
     // Static cache of already built menu trees.
-    $trees = &drupal_static('menu_build_tree', array());
+    $trees = &drupal_static(__METHOD__, array());
     $language_interface = \Drupal::languageManager()->getCurrentLanguage();
 
     // Build the cache id; sort parents to prevent duplicate storage and remove
@@ -728,7 +601,7 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
     if (isset($parameters['expanded'])) {
       sort($parameters['expanded']);
     }
-    $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
+    $tree_cid = 'book-links:' . $bid . ':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($trees[$tree_cid])) {
@@ -739,16 +612,14 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
     }
 
     if (!isset($trees[$tree_cid])) {
-      $query = \Drupal::entityQuery('menu_link');
-      for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
-        $query->sort('p' . $i, 'ASC');
+      $query = $this->connection->select('book');
+      $query->fields('book');
+      for ($i = 1; $i <= static::BOOK_MAX_DEPTH; $i++) {
+        $query->orderBy('p' . $i, 'ASC');
       }
-      $query->condition('menu_name', $menu_name);
+      $query->condition('bid', $bid);
       if (!empty($parameters['expanded'])) {
-        $query->condition('plid', $parameters['expanded'], 'IN');
-      }
-      elseif (!empty($parameters['only_active_trail'])) {
-        $query->condition('mlid', $parameters['active_trail'], 'IN');
+        $query->condition('pid', $parameters['expanded'], 'IN');
       }
       $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
       if ($min_depth != 1) {
@@ -766,8 +637,10 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
 
       // Build an ordered array of links using the query result object.
       $links = array();
-      if ($result = $query->execute()) {
-        $links = menu_link_load_multiple($result);
+      $result = $query->execute();
+      foreach ($result as $link) {
+        $link = (array) $link;
+        $links[$link['nid']] = $link;
       }
       $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
       $data['tree'] = $this->menu_tree_data($links, $active_trail, $min_depth);
@@ -775,7 +648,7 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
       $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']);
 
       // Cache the data, if it is not already in the cache.
-      \Drupal::cache('menu')->set($tree_cid, $data, Cache::PERMANENT, array('menu' => $menu_name));
+      \Drupal::cache('menu')->set($tree_cid, $data, Cache::PERMANENT, array('bid' => $bid));
       $trees[$tree_cid] = $data;
     }
 
@@ -783,23 +656,15 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
   }
 
   /**
-   * Collects node links from a given menu tree recursively.
-   *
-   * @param array $tree
-   *   The menu tree you wish to collect node links from.
-   * @param array $node_links
-   *   An array in which to store the collected node links.
+   * {@inheritdoc}
    */
   public function bookTreeCollectNodeLinks(&$tree, &$node_links) {
     // All book links are nodes.
     // @todo clean this up.
     foreach ($tree as $key => $v) {
-      if (!is_array($v['link']['route_parameters'])) {
-        $v['link']['route_parameters'] = unserialize($v['link']['route_parameters']);
-      }
-      if ($v['link']['route_name'] == 'node.view' && isset($v['link']['route_parameters']['node'])) {
-        $nid = $v['link']['route_parameters']['node'];
-        $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
+      if ($v['link']['nid']) {
+        $nid = $v['link']['nid'];
+        $node_links[$nid][$tree[$key]['link']['nid']] = &$tree[$key]['link'];
         $tree[$key]['link']['access'] = FALSE;
       }
       if ($tree[$key]['below']) {
@@ -809,13 +674,198 @@ public function bookTreeCollectNodeLinks(&$tree, &$node_links) {
   }
 
   /**
-   * Checks access and performs dynamic operations for each link in the tree.
+   * {@inheritdoc}
+   */
+  public function loadBookLink($nid, $translate = TRUE) {
+    $link = $this->connection->query("SELECT * FROM {book} WHERE nid = :nid", array(':nid' => $nid))->fetchAssoc();
+    if ($link && $translate) {
+      $this->bookLinkTranslate($link);
+    }
+    return $link;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveBookLink(array $link, $new) {
+    // Keep track of Book IDs for cache clear.
+    $affected_bids[$link['bid']] = $link['bid'];
+    $link += $this->getLinkDefaults($link['nid']);
+    if ($new) {
+      // Insert new.
+      $this->connection->insert('book')
+        ->fields(array(
+            'nid' => $link['nid'],
+            'bid' => $link['bid'],
+            'pid' => $link['pid'],
+            'weight' => $link['weight'],
+          ) + $this->getBookParents($link, (array) $this->loadBookLink($link['pid'], FALSE)))
+        ->execute();
+      // Check the has_children status of the parent.
+      $this->updateParent($link);
+    }
+    else {
+      $original = $this->loadBookLink($link['nid'], FALSE);
+      // Using the Book ID as the key keeps this unique.
+      $affected_bids[$original['bid']] = $original['bid'];
+      // Handle links that are moving.
+      if ($link['bid'] != $original['bid'] || $link['pid'] != $original['pid']) {
+        // Update the bid for this page and all children.
+        if ($link['pid'] == 0) {
+          $link['depth'] = 1;
+          $parent = array();
+        }
+        // In case the form did not specify a proper PID we use the BID as new
+        // parent.
+        elseif (($parent_link = $this->loadBookLink($link['pid'], FALSE)) && $parent_link['bid'] != $link['bid']) {
+          $link['pid'] = $link['bid'];
+          $parent = $this->loadBookLink($link['pid'], FALSE);
+          $link['depth'] = $parent['depth'] + 1;
+        }
+        else {
+          $parent = $this->loadBookLink($link['pid'], FALSE);
+          $link['depth'] = $parent['depth'] + 1;
+        }
+        $this->setParents($link, $parent);
+        $this->moveChildren($link, $original);
+
+        // Update the has_children status of the original parent.
+        $this->updateOriginalParent($original);
+        // Update the has_children status of the new parent.
+        $this->updateParent($link);
+      }
+      // Update the weight and pid.
+      $query = $this->connection->update('book');
+      $query->fields(array('weight' => $link['weight'], 'pid' => $link['pid'], 'bid' => $link['bid']));
+      $query->condition('nid', $link['nid']);
+      $query->execute();
+    }
+    foreach ($affected_bids as $bid) {
+      \Drupal::cache('menu')->deleteTags(array('bid' => $bid));
+    }
+  }
+
+  protected function moveChildren(array $link, array $original) {
+    $query = $this->connection->update('book');
+
+    $query->fields(array('bid' => $link['bid']));
+
+    $p = 'p1';
+    $expressions = array();
+    for ($i = 1; $i <= $link['depth']; $p = 'p' . ++$i) {
+      $expressions[] = array($p, ":p_$i", array(":p_$i" => $link[$p]));
+    }
+    $j = $original['depth'] + 1;
+    while ($i <= static::BOOK_MAX_DEPTH && $j <= static::BOOK_MAX_DEPTH) {
+      $expressions[] = array('p' . $i++, 'p' . $j++, array());
+    }
+    while ($i <= static::BOOK_MAX_DEPTH) {
+      $expressions[] = array('p' . $i++, 0, array());
+    }
+
+    $shift = $link['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('bid', $original['bid']);
+    $p = 'p1';
+    for ($i = 1; !empty($original[$p]); $p = 'p' . ++$i) {
+      $query->condition($p, $original[$p]);
+    }
+
+    $query->execute();
+  }
+
+  /**
+   * Sets the has_children flag of the parent of the node.
    *
-   * @param array $tree
-   *   The menu tree you wish to operate on.
-   * @param array $node_links
-   *   A collection of node link references generated from $tree by
-   *   menu_tree_collect_node_links().
+   * This method is mostly called when a book is moved/created etc. so we want
+   * to update the has_children flag of the parent book.
+   *
+   * @param array $link
+   *   The book which parent we want to update.
+   *
+   * @return bool
+   *   TRUE if there was no parent, otherwise TRUE on success, FALSE on failure.
+   */
+  protected function updateParent(array $link) {
+    if ($link['pid'] == 0) {
+      // Nothing to update.
+      return TRUE;
+    }
+    $query = $this->connection->update('book');
+    $query->fields(array('has_children' => 1))
+      ->condition('nid', $link['pid']);
+    return $query->execute();
+  }
+
+  /**
+   * Updates the has_children flag of the parent of the original node.
+   *
+   * This method is mostly called when a book is moved/created etc. so we want
+   * to update the has_children flag of the parent node.
+   *
+   * @param array $original
+   *   The book which parent we want to update.
+   *
+   * @return bool
+   *   TRUE if there was no parent, otherwise TRUE on success, FALSE on failure.
+   */
+  protected function updateOriginalParent(array $original) {
+    if ($original['pid'] == 0) {
+      // Nothing to update.
+      return TRUE;
+    }
+    // Check if at least one child exists in the table.
+    $parent_has_children = $this->connection->select('book', 'b')
+      ->condition('bid', $original['bid'])
+      ->condition('pid', $original['pid'])
+      ->condition('nid', $original['nid'], '<>')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+
+    $parent_has_children = ((bool) $parent_has_children) ? 1 : 0;
+    $query = $this->connection->update('book');
+    $query->fields(array('has_children' => $parent_has_children))
+        ->condition('nid', $original['pid']);
+    return $query->execute();
+  }
+
+  /**
+   * Sets the p1 through p9 properties for a book link being saved.
+   *
+   * @param array $link
+   *   A book link.
+   * @param array $parent
+   *   A book link.
+   */
+  protected function setParents(array &$link, array $parent) {
+    $i = 1;
+    while ($i < $link['depth']) {
+      $p = 'p' . $i++;
+      $link[$p] = $parent[$p];
+    }
+    $p = 'p' . $i++;
+    // The parent (p1 - p9) corresponding to the depth always equals the nid.
+    $link[$p] = $link['nid'];
+    while ($i <= static::BOOK_MAX_DEPTH) {
+      $p = 'p' . $i++;
+      $link[$p] = 0;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public function bookTreeCheckAccess(&$tree, $node_links = array()) {
     if ($node_links) {
@@ -845,7 +895,7 @@ protected function _menu_tree_check_access(&$tree) {
     $new_tree = array();
     foreach ($tree as $key => $v) {
       $item = &$tree[$key]['link'];
-      $this->_menu_link_translate($item);
+      $this->bookLinkTranslate($item);
       if ($item['access']) {
         if ($tree[$key]['below']) {
           $this->_menu_tree_check_access($tree[$key]['below']);
@@ -853,7 +903,7 @@ protected function _menu_tree_check_access(&$tree) {
         // 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];
+        $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['nid']] = $tree[$key];
       }
     }
     // Sort siblings in the tree based on the weights and localized titles.
@@ -862,46 +912,24 @@ protected function _menu_tree_check_access(&$tree) {
   }
 
   /**
-   * Provides menu link access control, translation, and argument handling.
-   *
-   * This function is similar to _menu_translate(), but it also does
-   * link-specific preparation (such as always calling to_arg() functions).
-   *
-   * @param $item
-   *   A menu link.
-   * @param bool $translate
-   *   (optional) Whether to try to translate a link containing dynamic path
-   *   argument placeholders (%) based on the menu router item of the current
-   *   path. Defaults to FALSE. Internally used for breadcrumbs.
-   *
-   * Note: copied from _menu_link_translate() in menu.inc, but reduced to the
-   * minimal code that's used.
+   * {@inheritdoc}
    */
-  protected function _menu_link_translate(&$item, $translate = FALSE) {
-    if (!is_array($item['options'])) {
-      $item['options'] = unserialize($item['options']);
-    }
-    if (!is_array($item['route_parameters'])) {
-      $item['route_parameters'] = unserialize($item['route_parameters']);
-    }
-    $item['href'] = $item['link_path'];
-    // menu_tree_check_access() may set this ahead of time for links to nodes.
-    if (!isset($item['access'])) {
-      $item['access'] = \Drupal::service('access_manager')->checkNamedRoute('node.view', array('node' => $item['route_parameters']['node']), \Drupal::currentUser());
+  public function bookLinkTranslate(&$link) {
+    $node = NULL;
+    // Access will already be set in the tree functions.
+    if (!isset($link['access'])) {
+      $node = $this->entityManager->getStorageController('node')->load($link['nid']);
+      $link['access'] = $node && $node->access('view');
     }
     // For performance, don't localize a link the user can't access.
-    if ($item['access']) {
-      // Inlined the code we use from _menu_item_localize().
-      $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 ($link['access']) {
+      // @todo - load the nodes en-mass rather than individually.
+      if (!$node) {
+        $node = $this->entityManager->getStorageController('node')->load($link['nid']);
       }
-      $item['title'] = $item['link_title'];
+      // The node label will be the value for the current user's language.
+      $link['title'] = $node->label();
+      $link['options'] = array();
     }
   }
 
@@ -948,9 +976,9 @@ protected function _menu_tree_data(&$links, $parents, $depth) {
     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);
+      $item['in_active_trail'] = in_array($item['nid'], $parents);
       // Add the current link to the tree.
-      $tree[$item['mlid']] = array(
+      $tree[$item['nid']] = array(
         'link' => $item,
         'below' => array(),
       );
@@ -960,11 +988,11 @@ protected function _menu_tree_data(&$links, $parents, $depth) {
       // Check whether the next link is the first in a new sub-tree.
       if ($next && $next['depth'] > $depth) {
         // Recursively call _menu_tree_data to build the sub-tree.
-        $tree[$item['mlid']]['below'] = $this->_menu_tree_data($links, $parents, $next['depth']);
+        $tree[$item['nid']]['below'] = $this->_menu_tree_data($links, $parents, $next['depth']);
         // Fetch next link after filling the sub-tree.
         $next = end($links);
       }
-      // Determine if we should exit the loop and return.
+      // Determine if we should exit the loop and $request = return.
       if (!$next || $next['depth'] < $depth) {
         break;
       }
@@ -973,23 +1001,13 @@ protected function _menu_tree_data(&$links, $parents, $depth) {
   }
 
   /**
-   * Gets the data representing a subtree of the book hierarchy.
-   *
-   * The root of the subtree will be the link passed as a parameter, so the
-   * returned tree will contain this item and all its descendents in the menu
-   * tree.
-   *
-   * @param $link
-   *   A fully loaded menu link.
-   *
-   * @return
-   *   A subtree of menu links in an array, in the order they should be rendered.
+   * {@inheritdoc}
    */
   public function bookMenuSubtreeData($link) {
-    $tree = &drupal_static(__FUNCTION__, array());
+    $tree = &drupal_static(__METHOD__, array());
 
-    // Generate a cache ID (cid) specific for this $menu_name and $link.
-    $cid = 'links:' . $link['menu_name'] . ':subtree-cid:' . $link['mlid'];
+    // Generate a cache ID (cid) specific for this $link.
+    $cid = 'book-links:subtree-cid:' . $link['nid'];
 
     if (!isset($tree[$cid])) {
       $cache = \Drupal::cache('menu')->get($cid);
@@ -1006,15 +1024,13 @@ public function bookMenuSubtreeData($link) {
 
       // If the subtree data was not in the cache, $data will be NULL.
       if (!isset($data)) {
-        $query = db_select('menu_links', 'ml', array('fetch' => \PDO::FETCH_ASSOC));
-        $query->join('book', 'b', 'ml.mlid = b.mlid');
+        $query = db_select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC));
         $query->fields('b');
-        $query->fields('ml');
-        $query->condition('menu_name', $link['menu_name']);
-        for ($i = 1; $i <= MENU_MAX_DEPTH && $link["p$i"]; ++$i) {
+        $query->condition('b.bid', $link['bid']);
+        for ($i = 1; $i <= static::BOOK_MAX_DEPTH && $link["p$i"]; ++$i) {
           $query->condition("p$i", $link["p$i"]);
         }
-        for ($i = 1; $i <= MENU_MAX_DEPTH; ++$i) {
+        for ($i = 1; $i <= static::BOOK_MAX_DEPTH; ++$i) {
           $query->orderBy("p$i");
         }
         $links = array();
@@ -1025,14 +1041,14 @@ public function bookMenuSubtreeData($link) {
         $data['node_links'] = array();
         $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']);
         // Compute the real cid for book subtree data.
-        $tree_cid = 'links:' . $item['menu_name'] . ':subtree-data:' . hash('sha256', serialize($data));
+        $tree_cid = 'book-links:subtree-data:' . hash('sha256', serialize($data));
         // Cache the data, if it is not already in the cache.
 
         if (!\Drupal::cache('menu')->get($tree_cid)) {
-          \Drupal::cache('menu')->set($tree_cid, $data);
+          \Drupal::cache('menu')->set($tree_cid, $data, Cache::PERMANENT, array('bid' => $link['bid']));
         }
         // Cache the cid of the (shared) data using the menu and item-specific cid.
-        \Drupal::cache('menu')->set($cid, $tree_cid);
+        \Drupal::cache('menu')->set($cid, $tree_cid, Cache::PERMANENT, array('bid' => $link['bid']));
       }
       // Check access for the current user to each item in the tree.
       $this->bookTreeCheckAccess($data['tree'], $data['node_links']);
@@ -1041,4 +1057,5 @@ public function bookMenuSubtreeData($link) {
 
     return $tree[$cid];
   }
+
 }
diff --git a/core/modules/book/lib/Drupal/book/BookManagerInterface.php b/core/modules/book/lib/Drupal/book/BookManagerInterface.php
new file mode 100644
index 0000000..129b1cf
--- /dev/null
+++ b/core/modules/book/lib/Drupal/book/BookManagerInterface.php
@@ -0,0 +1,246 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: dawehner
+ * Date: 2/10/14
+ * Time: 7:48 PM
+ */
+namespace Drupal\book;
+
+use Drupal\Core\Session\AccountInterface;
+use Drupal\node\NodeInterface;
+
+
+/**
+ * Book Manager Service.
+ */
+interface BookManagerInterface {
+
+  /**
+   * 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 int $bid
+   *   The Book ID to find links for.
+   * @param $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.
+   *
+   * Note: copied from menu_tree_all_data().
+   */
+  public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL);
+
+  /**
+   * Load a single book entry.
+   *
+   * @param int $nid
+   *   The node ID of the book.
+   * @param bool $translate
+   *   If TRUE, set access, title, and other elements.
+   *
+   * @return array
+   *   The book data of that node.
+   */
+  public function loadBookLink($nid, $translate = TRUE);
+
+  /**
+   * Returns an array of book pages in table of contents order.
+   *
+   * @param int $bid
+   *   The ID of the book whose pages are to be listed.
+   * @param int $depth_limit
+   *   Any link deeper than this value will be excluded (along with its children).
+   * @param array $exclude
+   *   (optional) An array of menu link ID values. Any link whose menu link ID is
+   *   in this array will be excluded (along with its children). Defaults to an
+   *   empty array.
+   *
+   * @return array
+   *   An array of (menu link ID, title) pairs for use as options for selecting a
+   *   book page.
+   */
+  public function getTableOfContents($bid, $depth_limit, array $exclude = array());
+
+  /**
+   * Finds the depth limit for items in the parent select.
+   *
+   * @param array $book_link
+   *   A fully loaded menu link that is part of the book hierarchy.
+   *
+   * @return int
+   *   The depth limit for items in the parent select.
+   */
+  public function getParentDepthLimit(array $book_link);
+
+  /**
+   * Collects node links from a given menu tree recursively.
+   *
+   * @param array $tree
+   *   The menu tree you wish to collect node links from.
+   * @param array $node_links
+   *   An array in which to store the collected node links.
+   */
+  public function bookTreeCollectNodeLinks(&$tree, &$node_links);
+
+  /**
+   * Provides menu link access control, translation, and argument handling.
+   *
+   * This function is similar to _menu_translate(), but it also does
+   * link-specific preparation (such as always calling to_arg() functions).
+   *
+   * @param array $link
+   *   A book link.
+   *
+   * Note: copied from _menu_link_translate() in menu.inc, but reduced to the
+   * minimal code that's used.
+   */
+  public function bookLinkTranslate(&$link);
+
+  /**
+   * Returns an array of all books.
+   *
+   * This list may be used for generating a list of all the books, or for building
+   * the options for a form select.
+   *
+   * @return
+   *   An array of all books.
+   */
+  public function getAllBooks();
+
+  /**
+   * Handles additions and updates to the book outline.
+   *
+   * This common helper function performs all additions and updates to the book
+   * outline through node addition, node editing, node deletion, or the outline
+   * tab.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node that is being saved, added, deleted, or moved.
+   *
+   * @return bool
+   *   TRUE if the book link was saved; FALSE otherwise.
+   */
+  public function updateOutline(NodeInterface $node);
+
+  /**
+   * Save a single book entry.
+   *
+   * @param array $link
+   *   The link data to save.
+   * @param bool $new
+   *   Is this a new book.
+   *
+   * @return array
+   *   The book data of that node.
+   */
+  public function saveBookLink(array $link, $new);
+
+  /**
+   * Returns an array with default values for a book page's menu link.
+   *
+   * @param string|int $nid
+   *   The ID of the node whose menu link is being created.
+   *
+   * @return array
+   *   The default values for the menu link.
+   */
+  public function getLinkDefaults($nid);
+
+  public function getBookParents(array $item, array $parent = array());
+
+  /**
+   * Builds the common elements of the book form for the node and outline forms.
+   *
+   * @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.
+   * @param \Drupal\node\NodeInterface $node
+   *   The node whose form is being viewed.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The account viewing the form.
+   * @param bool $collapsed
+   *   If TRUE, the fieldset start out collpased..
+   *
+   * @return array
+   *   The form structure, with the book elements added.
+   */
+  public function addFormElements(array $form, array &$form_state, NodeInterface $node, AccountInterface $account, $collapsed = TRUE);
+
+  /**
+   * Deletes node's entry from book table.
+   *
+   * @param int $nid
+   *   The nid to delete.
+   */
+  public function deleteFromBook($nid);
+
+  /**
+   * 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().
+   *
+   * @todo This was copied from menu_tree_output() but with some changes that
+   *   may be obsolete. Attempt to resolve the differences.
+   */
+  public function bookTreeOutput(array $tree);
+
+  /**
+   * Checks access and performs dynamic operations for each link in the tree.
+   *
+   * @param array $tree
+   *   The menu tree you wish to operate on.
+   * @param array $node_links
+   *   A collection of node link references generated from $tree by
+   *   menu_tree_collect_node_links().
+   */
+  public function bookTreeCheckAccess(&$tree, $node_links = array());
+
+  /**
+   * Gets the data representing a subtree of the book hierarchy.
+   *
+   * The root of the subtree will be the link passed as a parameter, so the
+   * returned tree will contain this item and all its descendents in the menu
+   * tree.
+   *
+   * @param $link
+   *   A fully loaded menu link.
+   *
+   * @return
+   *   A subtree of menu links in an array, in the order they should be rendered.
+   */
+  public function bookMenuSubtreeData($link);
+
+  /**
+   * Determines if a node can be removed from the book.
+   *
+   * A node can be removed from a book if it is actually in a book and it either
+   * is not a top-level page or is a top-level page with no children.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node to remove from the outline.
+   *
+   * @return bool
+   *   TRUE if a node can be removed from the book, FALSE otherwise.
+   */
+  public function checkNodeIsRemovable(NodeInterface $node);
+}
diff --git a/core/modules/book/lib/Drupal/book/Controller/BookController.php b/core/modules/book/lib/Drupal/book/Controller/BookController.php
index b9920d2..4067a74 100644
--- a/core/modules/book/lib/Drupal/book/Controller/BookController.php
+++ b/core/modules/book/lib/Drupal/book/Controller/BookController.php
@@ -9,6 +9,7 @@
 
 use Drupal\book\BookManager;
 use Drupal\book\BookExport;
+use Drupal\book\BookManagerInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\node\NodeInterface;
 use Symfony\Component\DependencyInjection\Container;
@@ -24,7 +25,7 @@ class BookController implements ContainerInjectionInterface {
   /**
    * The book manager.
    *
-   * @var \Drupal\book\BookManager
+   * @var BookManagerInterface
    */
   protected $bookManager;
 
@@ -38,12 +39,12 @@ class BookController implements ContainerInjectionInterface {
   /**
    * Constructs a BookController object.
    *
-   * @param \Drupal\book\BookManager $bookManager
+   * @param BookManagerInterface $bookManager
    *   The book manager.
    * @param \Drupal\book\BookExport $bookExport
    *   The book export service.
    */
-  public function __construct(BookManager $bookManager, BookExport $bookExport) {
+  public function __construct(BookManagerInterface $bookManager, BookExport $bookExport) {
     $this->bookManager = $bookManager;
     $this->bookExport = $bookExport;
   }
@@ -72,7 +73,7 @@ public function adminOverview() {
     // Add any recognized books to the table list.
     foreach ($this->bookManager->getAllBooks() as $book) {
       $row = array(
-        l($book['title'], $book['href'], $book['options']),
+        l($book['title'], $book['link_path'], isset($book['options']) ? $book['options'] : array()),
       );
       $links = array();
       $links['edit'] = array(
diff --git a/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php b/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php
index 07beef0..d1db3c9 100644
--- a/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php
+++ b/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\book\Form;
 
+use \Drupal\book\BookManager;
+use Drupal\book\BookManagerInterface;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
@@ -35,11 +37,11 @@ class BookAdminEditForm extends FormBase {
   protected $nodeStorage;
 
   /**
-   * The menu link storage controller.
+   * The book manager.
    *
-   * @var \Drupal\menu_link\MenuLinkStorageControllerInterface
+   * @var BookManagerInterface
    */
-  protected $menuLinkStorage;
+  protected $bookManager;
 
   /**
    * Constructs a new BookAdminEditForm.
@@ -48,13 +50,13 @@ class BookAdminEditForm extends FormBase {
    *   The menu cache object to be used by this controller.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $node_storage
    *   The custom block storage controller.
-   * @param \Drupal\menu_link\MenuLinkStorageControllerInterface $menu_link_storage
-   *   The custom block type storage controller.
+   * @param BookManagerInterface $book_manager
+   *   The book manager.
    */
-  public function __construct(CacheBackendInterface $cache, EntityStorageControllerInterface $node_storage, MenuLinkStorageControllerInterface $menu_link_storage) {
+  public function __construct(CacheBackendInterface $cache, EntityStorageControllerInterface $node_storage, BookManagerInterface $book_manager) {
     $this->cache = $cache;
     $this->nodeStorage = $node_storage;
-    $this->menuLinkStorage = $menu_link_storage;
+    $this->bookManager = $book_manager;
   }
 
   /**
@@ -65,7 +67,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('cache.menu'),
       $entity_manager->getStorageController('node'),
-      $entity_manager->getStorageController('menu_link')
+      $container->get('book.manager')
     );
   }
 
@@ -117,11 +119,11 @@ public function submitForm(array &$form, array &$form_state) {
         $values = $form_state['values']['table'][$key];
 
         // Update menu item if moved.
-        if ($row['plid']['#default_value'] != $values['plid'] || $row['weight']['#default_value'] != $values['weight']) {
-          $menu_link = $this->menuLinkStorage->load($values['mlid']);
-          $menu_link->weight = $values['weight'];
-          $menu_link->plid = $values['plid'];
-          $menu_link->save();
+        if ($row['pid']['#default_value'] != $values['pid'] || $row['weight']['#default_value'] != $values['weight']) {
+          $link = $this->bookManager->loadBookLink($values['nid'], FALSE);
+          $link['weight'] = $values['weight'];
+          $link['pid'] = $values['pid'];
+          $this->bookManager->saveBookLink($link, FALSE);
           $updated = TRUE;
         }
 
@@ -140,7 +142,7 @@ public function submitForm(array &$form, array &$form_state) {
     if ($updated) {
       // Flush static and cache.
       drupal_static_reset('book_menu_subtree_data');
-      $cid = 'links:' . $form['#node']->book['menu_name'] . ':subtree-cid:' . $form['#node']->book['mlid'];
+      $cid = 'book-links:subtree-cid:' . $form['#node']->book['nid'];
       $this->cache->delete($cid);
     }
 
@@ -202,10 +204,9 @@ protected function bookAdminTableTree(array $tree, array &$form) {
         '#item' => $data['link'],
         'nid' => array('#type' => 'value', '#value' => $data['link']['nid']),
         'depth' => array('#type' => 'value', '#value' => $data['link']['depth']),
-        'href' => array('#type' => 'value', '#value' => $data['link']['href']),
         'title' => array(
           '#type' => 'textfield',
-          '#default_value' => $data['link']['link_title'],
+          '#default_value' => $data['link']['title'],
           '#maxlength' => 255,
           '#size' => 40,
         ),
@@ -216,13 +217,13 @@ protected function bookAdminTableTree(array $tree, array &$form) {
           '#title' => $this->t('Weight for @title', array('@title' => $data['link']['title'])),
           '#title_display' => 'invisible',
         ),
-        'plid' => array(
+        'pid' => array(
           '#type' => 'hidden',
-          '#default_value' => $data['link']['plid'],
+          '#default_value' => $data['link']['pid'],
         ),
-        'mlid' => array(
+        'nid' => array(
           '#type' => 'hidden',
-          '#default_value' => $data['link']['mlid'],
+          '#default_value' => $data['link']['nid'],
         ),
       );
       if ($data['below']) {
diff --git a/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php
index aae3eac..78a5d28 100644
--- a/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php
+++ b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\book\Form;
 
+use Drupal\book\BookManagerInterface;
 use Drupal\Core\Entity\ContentEntityFormController;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\book\BookManager;
@@ -27,7 +28,7 @@ class BookOutlineForm extends ContentEntityFormController {
   /**
    * BookManager service.
    *
-   * @var \Drupal\book\BookManager
+   * @var BookManagerInterface
    */
   protected $bookManager;
 
@@ -36,10 +37,10 @@ class BookOutlineForm extends ContentEntityFormController {
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \Drupal\book\BookManager $book_manager
+   * @param BookManagerInterface $book_manager
    *   The BookManager service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, BookManager $book_manager) {
+  public function __construct(EntityManagerInterface $entity_manager, BookManagerInterface $book_manager) {
     parent::__construct($entity_manager);
     $this->bookManager = $book_manager;
   }
@@ -79,7 +80,8 @@ public function form(array $form, array &$form_state) {
     if (!isset($this->entity->book['parent_depth_limit'])) {
       $this->entity->book['parent_depth_limit'] = $this->bookManager->getParentDepthLimit($this->entity->book);
     }
-    $form = $this->bookManager->addFormElements($form, $form_state, $this->entity, $this->currentUser());
+    $form = $this->bookManager->addFormElements($form, $form_state, $this->entity, $this->currentUser(), FALSE);
+
 
     return $form;
   }
@@ -113,10 +115,9 @@ public function submit(array $form, array &$form_state) {
       return;
     }
 
-    $book_link['menu_name'] = $this->bookManager->createMenuName($book_link['bid']);
     $this->entity->book = $book_link;
     if ($this->bookManager->updateOutline($this->entity)) {
-      if ($this->entity->book['parent_mismatch']) {
+      if (isset($this->entity->book['parent_mismatch']) && $this->entity->book['parent_mismatch']) {
         // This will usually only happen when JS is disabled.
         drupal_set_message($this->t('The post has been added to the selected book. You may now position it relative to other pages.'));
         $form_state['redirect_route'] = $this->entity->urlInfo('book-outline-form');
diff --git a/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php b/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php
index ce84ea0..d370d7e 100644
--- a/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php
+++ b/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php
@@ -8,6 +8,7 @@
 namespace Drupal\book\Form;
 
 use Drupal\book\BookManager;
+use Drupal\book\BookManagerInterface;
 use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\node\NodeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -20,7 +21,7 @@ class BookRemoveForm extends ConfirmFormBase {
   /**
    * The book manager.
    *
-   * @var \Drupal\book\BookManager
+   * @var BookManagerInterface
    */
   protected $bookManager;
 
@@ -34,10 +35,10 @@ class BookRemoveForm extends ConfirmFormBase {
   /**
    * Constructs a BookRemoveForm object.
    *
-   * @param \Drupal\book\BookManager $book_manager
+   * @param BookManagerInterface $book_manager
    *   The book manager.
    */
-  public function __construct(BookManager $book_manager) {
+  public function __construct(BookManagerInterface $book_manager) {
     $this->bookManager = $book_manager;
   }
 
@@ -109,8 +110,7 @@ public function getCancelRoute() {
    */
   public function submitForm(array &$form, array &$form_state) {
     if ($this->bookManager->checkNodeIsRemovable($this->node)) {
-      menu_link_delete($this->node->book['mlid']);
-      $this->bookManager->deleteBook($this->node->id());
+      $this->bookManager->deleteFromBook($this->node->id());
       drupal_set_message($this->t('The post has been removed from the book.'));
     }
     $form_state['redirect_route'] = array(
diff --git a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php
index 456a993..4350e53 100644
--- a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php
+++ b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php
@@ -111,7 +111,7 @@ public function build() {
         if ($book['bid'] == $current_bid) {
           // If the current page is a node associated with a book, the menu
           // needs to be retrieved.
-          $data = \Drupal::service('book.manager')->bookTreeAllData($node->book['menu_name'], $node->book);
+          $data = \Drupal::service('book.manager')->bookTreeAllData($node->book['bid'], $node->book);
           $book_menus[$book_id] = \Drupal::service('book.manager')->bookTreeOutput($data);
         }
         else {
@@ -140,7 +140,7 @@ public function build() {
       $nid = $select->execute()->fetchField();
       // Only show the block if the user has view access for the top-level node.
       if ($nid) {
-        $tree = \Drupal::service('book.manager')->bookTreeAllData($node->book['menu_name'], $node->book);
+        $tree = \Drupal::service('book.manager')->bookTreeAllData($node->book['bid'], $node->book);
         // There should only be one element at the top level.
         $data = array_shift($tree);
         $below = \Drupal::service('book.manager')->bookTreeOutput($data['below']);
diff --git a/core/modules/book/lib/Drupal/book/Tests/BookTest.php b/core/modules/book/lib/Drupal/book/Tests/BookTest.php
index 21fc988..aa4758a 100644
--- a/core/modules/book/lib/Drupal/book/Tests/BookTest.php
+++ b/core/modules/book/lib/Drupal/book/Tests/BookTest.php
@@ -92,8 +92,8 @@ function createBook() {
      */
     $nodes = array();
     $nodes[] = $this->createBookNode($book->id()); // Node 0.
-    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['mlid']); // Node 1.
-    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['mlid']); // Node 2.
+    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 1.
+    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 2.
     $nodes[] = $this->createBookNode($book->id()); // Node 3.
     $nodes[] = $this->createBookNode($book->id()); // Node 4.
 
@@ -122,7 +122,26 @@ function testBook() {
     $this->checkBookNode($nodes[4], NULL, $nodes[3], $book, FALSE, array($book));
 
     $this->drupalLogout();
+    $this->drupalLogin($this->book_author);
+    /*
+     * Add Node 5 under Node 3.
+     * Book
+     *  |- Node 0
+     *   |- Node 1
+     *   |- Node 2
+     *  |- Node 3
+     *   |- Node 5
+     *  |- Node 4
+     */
+
 
+    $nodes[] = $this->createBookNode($book->id(), $nodes[3]->book['nid']); // Node 5.
+    $this->drupalLogout();
+    $this->drupalLogin($this->web_user);
+    // Verify the new outline - make sure we don't get stale cached data.
+    $this->checkBookNode($nodes[3], array($nodes[5]), $nodes[2], $book, $nodes[5], array($book));
+    $this->checkBookNode($nodes[4], NULL, $nodes[5], $book, FALSE, array($book));
+    $this->drupalLogout();
     // Create a second book, and move an existing book page into it.
     $this->drupalLogin($this->book_author);
     $other_book = $this->createBookNode('new');
@@ -250,8 +269,11 @@ function createBookNode($book_nid, $parent = NULL) {
     if ($parent !== NULL) {
       $this->drupalPostForm('node/add/book', $edit, t('Change book (update list of parents)'));
 
-      $edit['book[plid]'] = $parent;
+      $edit['book[pid]'] = $parent;
       $this->drupalPostForm(NULL, $edit, t('Save'));
+      // Make sure the parent was flagged as having children.
+      $parent_node = \Drupal::entityManager()->getStorageController('node')->loadUnchanged($parent);
+      $this->assertFalse(empty($parent_node->book['has_children']), 'Parent node is marked as having children');
     }
     else {
       $this->drupalPostForm('node/add/book', $edit, t('Save'));
@@ -493,7 +515,7 @@ public function testBookOrdering() {
     $this->drupalLogin($this->admin_user);
     $node1 = $this->createBookNode($book->id());
     $node2 = $this->createBookNode($book->id());
-    $plid = $node1->book['mlid'];
+    $pid = $node1->book['nid'];
 
     // Head to admin screen and attempt to re-order.
     $this->drupalGet('admin/structure/book/' . $book->id());
@@ -501,19 +523,42 @@ public function testBookOrdering() {
       "table[book-admin-{$node1->id()}][weight]" => 1,
       "table[book-admin-{$node2->id()}][weight]" => 2,
       // Put node 2 under node 1.
-      "table[book-admin-{$node2->id()}][plid]" => $plid,
+      "table[book-admin-{$node2->id()}][pid]" => $pid,
     );
     $this->drupalPostForm(NULL, $edit, t('Save book pages'));
     // Verify weight was updated.
     $this->assertFieldByName("table[book-admin-{$node1->id()}][weight]", 1);
     $this->assertFieldByName("table[book-admin-{$node2->id()}][weight]", 2);
-    $this->assertFieldByName("table[book-admin-{$node2->id()}][plid]", $plid);
+    $this->assertFieldByName("table[book-admin-{$node2->id()}][pid]", $pid);
   }
 
   /**
    * Tests outline of a book.
    */
   public function testBookOutline() {
+    $this->drupalLogin($this->book_author);
+
+    // Create new node not yet a book.
+    $empty_book = $this->drupalCreateNode(array('type' => 'book'));
+    $this->drupalGet('node/' . $empty_book->id() . '/outline');
+    $this->assertNoLink(t('Book outline'), 'Book Author is not allowed to outline');
+
+    $this->drupalLogin($this->admin_user);
+    $this->drupalGet('node/' . $empty_book->id() . '/outline');
+    $this->assertRaw(t('Book outline'));
+    $this->assertOptionSelected('edit-book-bid', 0, 'Node does not belong to a book');
+
+    $edit = array();
+    $edit['book[bid]'] = '1';
+    $this->drupalPostForm('node/' . $empty_book->id() . '/outline', $edit, t('Add to book outline'));
+    $node = \Drupal::entityManager()->getStorageController('node')->load($empty_book->id());
+    // Test the book array.
+    $this->assertEqual($node->book['nid'], $empty_book->id());
+    $this->assertEqual($node->book['bid'], $empty_book->id());
+    $this->assertEqual($node->book['depth'], 1);
+    $this->assertEqual($node->book['p1'], $empty_book->id());
+    $this->assertEqual($node->book['pid'], '0');
+
     // Create new book.
     $this->drupalLogin($this->book_author);
     $book = $this->createBookNode('new');
@@ -521,5 +566,24 @@ public function testBookOutline() {
     $this->drupalLogin($this->admin_user);
     $this->drupalGet('node/' . $book->id() . '/outline');
     $this->assertRaw(t('Book outline'));
+
+    // Create a new node and set the book after the node was created.
+    $node = $this->drupalCreateNode(array('type' => 'book'));
+    $edit = array();
+    $edit['book[bid]'] = $node->id();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $node = \Drupal::entityManager()->getStorageController('node')->load($node->id());
+
+    // Test the book array.
+    $this->assertEqual($node->book['nid'], $node->id());
+    $this->assertEqual($node->book['bid'], $node->id());
+    $this->assertEqual($node->book['depth'], 1);
+    $this->assertEqual($node->book['p1'], $node->id());
+    $this->assertEqual($node->book['pid'], '0');
+
+    // Test the form itself.
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertOptionSelected('edit-book-bid', $node->id());
   }
+
 }
diff --git a/core/modules/book/tests/Drupal/book/Tests/BookManagerTest.php b/core/modules/book/tests/Drupal/book/Tests/BookManagerTest.php
new file mode 100644
index 0000000..e6ec790
--- /dev/null
+++ b/core/modules/book/tests/Drupal/book/Tests/BookManagerTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\book\Tests\BookManagerTest.
+ */
+
+namespace Drupal\book\Tests;
+
+use Drupal\book\BookManager;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the book manager.
+ *
+ * @coversDefaultClass \Drupal\book\BookManager
+ */
+class BookManagerTest extends UnitTestCase {
+
+  /**
+   * The mocked database connection.
+   *
+   * @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $connection;
+
+  /**
+   * The mocked entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The mocked config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $configFactory;
+
+  /**
+   * The mocked translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translation;
+
+  /**
+   * The tested book manager.
+   *
+   * @var \Drupal\book\BookManager
+   */
+  protected $bookManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Book manager',
+      'description' => 'Test the book manager.',
+      'group' => 'Book',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $this->translation = $this->getStringTranslationStub();
+    $this->configFactory = $this->getConfigFactoryStub(array());
+    $this->bookManager = new BookManager($this->connection, $this->entityManager, $this->translation, $this->configFactory);
+  }
+
+  /**
+   * Tests the getBookParents() method.
+   *
+   * @dataProvider providerTestGetBookParents
+   */
+  public function testGetBookParents($book, $parent, $expected) {
+    $this->assertEquals($expected, $this->bookManager->getBookParents($book, $parent));
+  }
+
+  /**
+   * Provides test data for testGetBookParents.
+   *
+   * @return array
+   */
+  public function providerTestGetBookParents() {
+    $empty = array(
+      'p1' => 0,
+      'p2' => 0,
+      'p3' => 0,
+      'p4' => 0,
+      'p5' => 0,
+      'p6' => 0,
+      'p7' => 0,
+      'p8' => 0,
+      'p9' => 0,
+    );
+    return array(
+      // Provides a book without an existing parent.
+      array(
+        array('pid' => 0, 'nid' => 12),
+        array(),
+        array('depth' => 1, 'p1' => 12) + $empty,
+      ),
+      // Provides a book with an existing parent.
+      array(
+        array('pid' => 11, 'nid' => 12),
+        array('nid' => 11, 'depth' => 1, 'p1' => 11,),
+        array('depth' => 2, 'p1' => 11, 'p2' => 12) + $empty,
+      ),
+      // Provides a book with two existing parents.
+      array(
+        array('pid' => 11, 'nid' => 12),
+        array('nid' => 11, 'depth' => 2, 'p1' => 10, 'p2' => 11),
+        array('depth' => 3, 'p1' => 10, 'p2' => 11, 'p3' => 12) + $empty,
+      ),
+    );
+  }
+
+}
