Commit 0ca87dbb authored by alexpott's avatar alexpott

Issue #2084421 by dawehner, pwolanin, YesCT, clemens.tolboom, Berdir, Xano,...

Issue #2084421 by dawehner, pwolanin, YesCT, clemens.tolboom, Berdir, Xano, Sutharsan: Phase 2 - Decouple book module schema from menu links.
parent 86394187
......@@ -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,
),
......
......@@ -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'),
),
);
......
This diff is collapsed.
......@@ -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 \Drupal\book\BookManagerInterface
*/
protected $bookManager;
/**
* Constructs a BookNodeIsRemovableAccessCheck object.
*
* @param \Drupal\book\BookManager $book_manager
* @param \Drupal\book\BookManagerInterface $book_manager
* Book Manager Service.
*/
public function __construct(BookManager $book_manager) {
public function __construct(BookManagerInterface $book_manager) {
$this->bookManager = $book_manager;
}
......
......@@ -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++;
......
<?php
/**
* @file
* Contains \Drupal\book\BookManagerInterface.
*/
namespace Drupal\book;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
/**
* Provides an interface defining a book manager.
*/
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 array|null $link
* (optional) 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|null $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: based on menu_tree_all_data().
*/
public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL);
/**
* Loads 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 array
* 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);
/**
* Saves 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 starts out collapsed.
*
* @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 book 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 descendants in the menu
* tree.
*
* @param array $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);
}
......@@ -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(
......
......@@ -7,6 +7,7 @@
namespace Drupal\book\Form;
use Drupal\book\BookManagerInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityStorageControllerInterface;
......@@ -35,11 +36,11 @@ class BookAdminEditForm extends FormBase {
protected $nodeStorage;
/**
* The menu link storage controller.
* The book manager.
*
* @var \Drupal\menu_link\MenuLinkStorageControllerInterface
* @var \Drupal\book\BookManagerInterface
*/
protected $menuLinkStorage;
protected $bookManager;
/**
* Constructs a new BookAdminEditForm.
......@@ -48,13 +49,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 \Drupal\book\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 +66,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 +118,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 +141,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 +203,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,