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) { ...@@ -29,18 +29,18 @@ function theme_book_admin_table($variables) {
$access = \Drupal::currentUser()->hasPermission('administer nodes'); $access = \Drupal::currentUser()->hasPermission('administer nodes');
foreach (element_children($form) as $key) { foreach (element_children($form) as $key) {
$nid = $form[$key]['nid']['#value']; $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. // Add special classes to be used with tabledrag.js.
$form[$key]['plid']['#attributes']['class'] = array('book-plid'); $form[$key]['pid']['#attributes']['class'] = array('book-pid');
$form[$key]['mlid']['#attributes']['class'] = array('book-mlid'); $form[$key]['nid']['#attributes']['class'] = array('book-nid');
$form[$key]['weight']['#attributes']['class'] = array('book-weight'); $form[$key]['weight']['#attributes']['class'] = array('book-weight');
$indentation = array('#theme' => 'indentation', '#size' => $form[$key]['depth']['#value'] - 2); $indentation = array('#theme' => 'indentation', '#size' => $form[$key]['depth']['#value'] - 2);
$data = array( $data = array(
drupal_render($indentation) . drupal_render($form[$key]['title']), drupal_render($indentation) . drupal_render($form[$key]['title']),
drupal_render($form[$key]['weight']), 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 = array();
$links['view'] = array( $links['view'] = array(
...@@ -84,9 +84,9 @@ function theme_book_admin_table($variables) { ...@@ -84,9 +84,9 @@ function theme_book_admin_table($variables) {
array( array(
'action' => 'match', 'action' => 'match',
'relationship' => 'parent', 'relationship' => 'parent',
'group' => 'book-plid', 'group' => 'book-pid',
'subgroup' => 'book-plid', 'subgroup' => 'book-pid',
'source' => 'book-mlid', 'source' => 'book-nid',
'hidden' => TRUE, 'hidden' => TRUE,
'limit' => MENU_MAX_DEPTH - 2, 'limit' => MENU_MAX_DEPTH - 2,
), ),
......
...@@ -23,34 +23,114 @@ function book_schema() { ...@@ -23,34 +23,114 @@ function book_schema() {
$schema['book'] = array( $schema['book'] = array(
'description' => 'Stores book outline information. Uniquely connects each node in the outline to a link in {menu_links}', 'description' => 'Stores book outline information. Uniquely connects each node in the outline to a link in {menu_links}',
'fields' => array( 'fields' => array(
'mlid' => array( 'nid' => array(
'type' => 'int', 'type' => 'int',
'unsigned' => TRUE, 'unsigned' => TRUE,
'not null' => TRUE, 'not null' => TRUE,
'default' => 0, 'default' => 0,
'description' => "The book page's {menu_links}.mlid.", 'description' => "The book page's {node}.nid.",
), ),
'nid' => array( 'bid' => array(
'type' => 'int', 'type' => 'int',
'unsigned' => TRUE, 'unsigned' => TRUE,
'not null' => TRUE, 'not null' => TRUE,
'default' => 0, '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', 'type' => 'int',
'unsigned' => TRUE, 'unsigned' => TRUE,
'not null' => TRUE, 'not null' => TRUE,
'default' => 0, 'default' => 0,
'description' => "The book ID is the {book}.nid of the top-level page.",
), ),
'p9' => array(
'description' => 'The ninth nid in the materialized path. See p1.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
), ),
'primary key' => array('mlid'),
'unique keys' => array(
'nid' => array('nid'),
), ),
'primary key' => array('nid'),
'indexes' => array( 'indexes' => array(
'bid' => array('bid'), 'book_parents' => array('bid', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
), ),
); );
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Allows users to create and organize related content in an outline. * Allows users to create and organize related content in an outline.
*/ */
use Drupal\book\BookManagerInterface;
use Drupal\Component\Utility\String; use Drupal\Component\Utility\String;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\node\NodeInterface; use Drupal\node\NodeInterface;
...@@ -40,29 +41,6 @@ function book_help($path, $arg) { ...@@ -40,29 +41,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(). * Implements hook_theme().
*/ */
...@@ -72,6 +50,9 @@ function book_theme() { ...@@ -72,6 +50,9 @@ function book_theme() {
'variables' => array('book_link' => NULL), 'variables' => array('book_link' => NULL),
'template' => 'book-navigation', 'template' => 'book-navigation',
), ),
'book_link' => array(
'render element' => 'element',
),
'book_export_html' => array( 'book_export_html' => array(
'variables' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL), 'variables' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL),
'template' => 'book-export-html', 'template' => 'book-export-html',
...@@ -138,7 +119,7 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$ ...@@ -138,7 +119,7 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$
$links['book_add_child'] = array( $links['book_add_child'] = array(
'title' => t('Add child page'), 'title' => t('Add child page'),
'href' => 'node/add/' . $child_type, '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) { ...@@ -222,14 +203,15 @@ function book_form_node_form_alter(&$form, &$form_state, $form_id) {
$account = \Drupal::currentUser(); $account = \Drupal::currentUser();
$access = $account->hasPermission('administer book outlines'); $access = $account->hasPermission('administer book outlines');
if (!$access) { 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. // Already in the book hierarchy, or this node type is allowed.
$access = TRUE; $access = TRUE;
} }
} }
if ($access) { 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 // 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 // JavaScript is disabled, add a submit button to do that. book.admin.css hides
// this button when JavaScript is enabled. // this button when JavaScript is enabled.
...@@ -283,7 +265,7 @@ function book_pick_book_nojs_submit($form, &$form_state) { ...@@ -283,7 +265,7 @@ function book_pick_book_nojs_submit($form, &$form_state) {
* The rendered parent page select element. * The rendered parent page select element.
*/ */
function book_form_update($form, $form_state) { 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) { ...@@ -302,14 +284,14 @@ function book_form_update($form, $form_state) {
function book_get_flat_menu($book_link) { function book_get_flat_menu($book_link) {
$flat = &drupal_static(__FUNCTION__, array()); $flat = &drupal_static(__FUNCTION__, array());
if (!isset($flat[$book_link['mlid']])) { if (!isset($flat[$book_link['nid']])) {
// Call bookTreeAllData() to take advantage of the menu system's caching. // Call bookTreeAllData() to take advantage of caching.
$tree = \Drupal::service('book.manager')->bookTreeAllData($book_link['menu_name'], $book_link, $book_link['depth'] + 1); $tree = \Drupal::service('book.manager')->bookTreeAllData($book_link['bid'], $book_link, $book_link['depth'] + 1);
$flat[$book_link['mlid']] = array(); $flat[$book_link['nid']] = array();
_book_flatten_menu($tree, $flat[$book_link['mlid']]); _book_flatten_menu($tree, $flat[$book_link['nid']]);
} }
return $flat[$book_link['mlid']]; return $flat[$book_link['nid']];
} }
/** /**
...@@ -324,13 +306,11 @@ function book_get_flat_menu($book_link) { ...@@ -324,13 +306,11 @@ function book_get_flat_menu($book_link) {
*/ */
function _book_flatten_menu($tree, &$flat) { function _book_flatten_menu($tree, &$flat) {
foreach ($tree as $data) { foreach ($tree as $data) {
if (!$data['link']['hidden']) { $flat[$data['link']['nid']] = $data['link'];
$flat[$data['link']['mlid']] = $data['link'];
if ($data['below']) { if ($data['below']) {
_book_flatten_menu($data['below'], $flat); _book_flatten_menu($data['below'], $flat);
} }
} }
}
} }
/** /**
...@@ -345,31 +325,34 @@ function _book_flatten_menu($tree, &$flat) { ...@@ -345,31 +325,34 @@ function _book_flatten_menu($tree, &$flat) {
*/ */
function book_prev($book_link) { function book_prev($book_link) {
// If the parent is zero, we are at the start of a book. // 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; return NULL;
} }
$flat = book_get_flat_menu($book_link);
// Assigning the array to $flat resets the array pointer for use with each(). // Assigning the array to $flat resets the array pointer for use with each().
$flat = book_get_flat_menu($book_link);
$curr = NULL; $curr = NULL;
do { do {
$prev = $curr; $prev = $curr;
list($key, $curr) = each($flat); 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 \Drupal\book\BookManagerInterface $book_manager */
$book_manager = \Drupal::service('book.manager');
// The previous page in the book may be a child of the previous visible link. // 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. // 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); $data = array_shift($tree);
// The link of interest is the last child - iterate to find the deepest one. // The link of interest is the last child - iterate to find the deepest one.
while ($data['below']) { while ($data['below']) {
$data = end($data['below']); $data = end($data['below']);
} }
$book_manager->bookLinkTranslate($data['link']);
return $data['link']; return $data['link'];
} }
else { else {
$book_manager->bookLinkTranslate($prev);
return $prev; return $prev;
} }
} }
...@@ -382,19 +365,23 @@ function book_prev($book_link) { ...@@ -382,19 +365,23 @@ function book_prev($book_link) {
* A fully loaded menu link that is part of the book hierarchy. * A fully loaded menu link that is part of the book hierarchy.
* *
* @return * @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. * $book_link.
*/ */
function book_next($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(). // Assigning the array to $flat resets the array pointer for use with each().
$flat = book_get_flat_menu($book_link);
do { do {
list($key, ) = each($flat); list($key, ) = each($flat);
} }
while ($key && $key != $book_link['mlid']); while ($key && $key != $book_link['nid']);
if ($key == $book_link['mlid']) { if ($key == $book_link['nid']) {
return current($flat); $next = current($flat);
if ($next) {
\Drupal::service('book.manager')->bookLinkTranslate($next);
}
return $next;
} }
} }
...@@ -417,9 +404,9 @@ function book_children($book_link) { ...@@ -417,9 +404,9 @@ function book_children($book_link) {
do { do {
$link = array_shift($flat); $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. // 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['link'] = $link;
$data['below'] = ''; $data['below'] = '';
$children[] = $data; $children[] = $data;
...@@ -437,12 +424,11 @@ function book_children($book_link) { ...@@ -437,12 +424,11 @@ function book_children($book_link) {
* Implements hook_node_load(). * Implements hook_node_load().
*/ */
function book_node_load($nodes) { 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) { foreach ($result as $record) {
$nodes[$record['nid']]->book = $record; $nodes[$record['nid']]->book = $record;
$nodes[$record['nid']]->book['href'] = $record['link_path']; $nodes[$record['nid']]->book['link_path'] = 'node/' . $record['nid'];
$nodes[$record['nid']]->book['title'] = $record['link_title']; $nodes[$record['nid']]->book['link_title'] = $nodes[$record['nid']]->label();
$nodes[$record['nid']]->book['options'] = unserialize($record['options']);
} }
} }
...@@ -476,7 +462,7 @@ function book_node_presave(EntityInterface $node) { ...@@ -476,7 +462,7 @@ function book_node_presave(EntityInterface $node) {
} }
// Make sure a new node gets a new menu link. // Make sure a new node gets a new menu link.
if ($node->isNew()) { if ($node->isNew()) {
$node->book['mlid'] = NULL; $node->book['nid'] = NULL;
} }
} }
...@@ -490,8 +476,6 @@ function book_node_insert(EntityInterface $node) { ...@@ -490,8 +476,6 @@ function book_node_insert(EntityInterface $node) {
// New nodes that are their own book. // New nodes that are their own book.
$node->book['bid'] = $node->id(); $node->book['bid'] = $node->id();
} }
$node->book['nid'] = $node->id();
$node->book['menu_name'] = $book_manager->createMenuName($node->book['bid']);
$book_manager->updateOutline($node); $book_manager->updateOutline($node);
} }
} }
...@@ -506,8 +490,6 @@ function book_node_update(EntityInterface $node) { ...@@ -506,8 +490,6 @@ function book_node_update(EntityInterface $node) {
// New nodes that are their own book. // New nodes that are their own book.
$node->book['bid'] = $node->id(); $node->book['bid'] = $node->id();
} }
$node->book['nid'] = $node->id();
$node->book['menu_name'] = $book_manager->createMenuName($node->book['bid']);
$book_manager->updateOutline($node); $book_manager->updateOutline($node);
} }
} }
...@@ -517,23 +499,9 @@ function book_node_update(EntityInterface $node) { ...@@ -517,23 +499,9 @@ function book_node_update(EntityInterface $node) {
*/ */
function book_node_predelete(EntityInterface $node) { function book_node_predelete(EntityInterface $node) {
if (!empty($node->book['bid'])) { if (!empty($node->book['bid'])) {
if ($node->id() == $node->book['bid']) { /** @var \Drupal\book\BookManagerInterface $book_manager */
// Handle deletion of a top-level post. $book_manager = \Drupal::service('book.manager');
$result = db_query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = :plid", array( $book_manager->deleteFromBook($node->book['nid']);
':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');
} }
} }
...@@ -552,12 +520,11 @@ function book_node_prepare_form(NodeInterface $node, $form_display, $operation, ...@@ -552,12 +520,11 @@ function book_node_prepare_form(NodeInterface $node, $form_display, $operation,
$query = \Drupal::request()->query; $query = \Drupal::request()->query;
if ($node->isNew() && !is_null($query->get('parent')) && is_numeric($query->get('parent'))) { if ($node->isNew() && !is_null($query->get('parent')) && is_numeric($query->get('parent'))) {
// Handle "Add child page" links: // Handle "Add child page" links:
$parent = book_link_load($query->get('parent')); $parent = $book_manager->loadBookLink($query->get('parent'), TRUE);
if ($parent && $parent['access']) { if ($parent && $parent['access']) {
$node->book['bid'] = $parent['bid']; $node->book['bid'] = $parent['bid'];
$node->book['plid'] = $parent['mlid']; $node->book['pid'] = $parent['nid'];
$node->book['menu_name'] = $parent['menu_name'];
} }
} }
// Set defaults. // Set defaults.
...@@ -633,7 +600,7 @@ function template_preprocess_book_all_books_block(&$variables) { ...@@ -633,7 +600,7 @@ function template_preprocess_book_all_books_block(&$variables) {
* @param array $variables * @param array $variables
* An associative array containing the following key: * An associative array containing the following key:
* - book_link: An associative array of book link properties. * - 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) { function template_preprocess_book_navigation(&$variables) {
$book_link = $variables['book_link']; $book_link = $variables['book_link'];
...@@ -641,17 +608,17 @@ function template_preprocess_book_navigation(&$variables) { ...@@ -641,17 +608,17 @@ function template_preprocess_book_navigation(&$variables) {
// Provide extra variables for themers. Not needed by default. // Provide extra variables for themers. Not needed by default.
$variables['book_id'] = $book_link['bid']; $variables['book_id'] = $book_link['bid'];
$variables['book_title'] = String::checkPlain($book_link['link_title']); $variables['book_title'] = String::checkPlain($book_link['link_title']);
$variables['book_url'] = 'node/' . $book_link