Commit 0da52046 authored by catch's avatar catch

Issue #2209025 by dawehner, fgm, pwolanin: Phase 3 - Make the BookManager...

Issue #2209025 by dawehner, fgm, pwolanin: Phase 3 - Make the BookManager interface more coherent, move more code out of book.module.
parent a6658cdd
......@@ -147,21 +147,6 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$
}
}
/**
* Returns an array of all books.
*
* @todo Remove in favor of BookManager Service. http://drupal.org/node/1963894
*
* 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.
*/
function book_get_books() {
return \Drupal::service('book.manager')->getAllBooks();
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for node_form().
*
......@@ -244,164 +229,14 @@ function book_form_update($form, $form_state) {
return $form['book']['pid'];
}
/**
* Gets the book menu tree for a page and returns it as a linear array.
*
* @param $book_link
* A fully loaded menu link that is part of the book hierarchy.
*
* @return
* A linear array of menu links in the order that the links are shown in the
* menu, so the previous and next pages are the elements before and after the
* element corresponding to the current node. The children of the current node
* (if any) will come immediately after it in the array, and links will only
* be fetched as deep as one level deeper than $book_link.
*/
function book_get_flat_menu($book_link) {
$flat = &drupal_static(__FUNCTION__, array());
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['nid']];
}
/**
* Recursively converts a tree of menu links to a flat array.
*
* @param $tree
* A tree of menu links in an array.
* @param $flat
* A flat array of the menu links from $tree, passed by reference.
*
* @see book_get_flat_menu().
*/
function _book_flatten_menu($tree, &$flat) {
foreach ($tree as $data) {
$flat[$data['link']['nid']] = $data['link'];
if ($data['below']) {
_book_flatten_menu($data['below'], $flat);
}
}
}
/**
* Fetches the menu link for the previous page of the book.
*
* @param $book_link
* A fully loaded menu link that is part of the book hierarchy.
*
* @return
* A fully loaded menu link for the page before the one represented in
* $book_link.
*/
function book_prev($book_link) {
// If the parent is zero, we are at the start of a book.
if ($book_link['pid'] == 0) {
return NULL;
}
// 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['nid']);
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.
if ($prev['depth'] == $book_link['depth']) {
// The subtree will have only one link at the top level - get its data.
$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;
}
}
}
/**
* Fetches the menu link for the next page of the book.
*
* @param $book_link
* A fully loaded menu link that is part of the book hierarchy.
*
* @return
* A fully loaded book link for the page after the one represented in
* $book_link.
*/
function book_next($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['nid']);
if ($key == $book_link['nid']) {
$next = current($flat);
if ($next) {
\Drupal::service('book.manager')->bookLinkTranslate($next);
}
return $next;
}
}
/**
* Formats the menu links for the child pages of the current page.
*
* @param $book_link
* A fully loaded menu link that is part of the book hierarchy.
*
* @return
* HTML for the links to the child pages of the current page.
*/
function book_children($book_link) {
$flat = book_get_flat_menu($book_link);
$children = array();
if ($book_link['has_children']) {
// Walk through the array until we find the current page.
do {
$link = array_shift($flat);
}
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['pid'] == $book_link['nid']) {
$data['link'] = $link;
$data['below'] = '';
$children[] = $data;
}
}
if ($children) {
$elements = \Drupal::service('book.manager')->bookTreeOutput($children);
return drupal_render($elements);
}
return '';
}
/**
* Implements hook_node_load().
*/
function book_node_load($nodes) {
$result = db_query("SELECT * FROM {book} WHERE nid IN (:nids)", array(':nids' => array_keys($nodes)), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $record) {
/** @var \Drupal\book\BookManagerInterface $book_manager */
$book_manager = \Drupal::service('book.manager');
$links = $book_manager->loadBookLinks(array_keys($nodes), FALSE);
foreach ($links as $record) {
$nodes[$record['nid']]->book = $record;
$nodes[$record['nid']]->book['link_path'] = 'node/' . $record['nid'];
$nodes[$record['nid']]->book['link_title'] = $nodes[$record['nid']]->label();
......@@ -442,28 +277,18 @@ function book_node_presave(EntityInterface $node) {
* Implements hook_node_insert().
*/
function book_node_insert(EntityInterface $node) {
/** @var \Drupal\book\BookManagerInterface $book_manager */
$book_manager = \Drupal::service('book.manager');
if (!empty($node->book['bid'])) {
if ($node->book['bid'] == 'new') {
// New nodes that are their own book.
$node->book['bid'] = $node->id();
}
$book_manager->updateOutline($node);
}
$book_manager->updateOutline($node);
}
/**
* Implements hook_node_update().
*/
function book_node_update(EntityInterface $node) {
/** @var \Drupal\book\BookManagerInterface $book_manager */
$book_manager = \Drupal::service('book.manager');
if (!empty($node->book['bid'])) {
if ($node->book['bid'] == 'new') {
// New nodes that are their own book.
$node->book['bid'] = $node->id();
}
$book_manager->updateOutline($node);
}
$book_manager->updateOutline($node);
}
/**
......@@ -481,7 +306,7 @@ function book_node_predelete(EntityInterface $node) {
* Implements hook_node_prepare_form().
*/
function book_node_prepare_form(NodeInterface $node, $operation, array &$form_state) {
// Get BookManager service
/** @var \Drupal\book\BookManagerInterface $book_manager */
$book_manager = \Drupal::service('book.manager');
// Prepare defaults for the add/edit form.
......@@ -584,12 +409,15 @@ function template_preprocess_book_navigation(&$variables) {
$variables['current_depth'] = $book_link['depth'];
$variables['tree'] = '';
/** @var \Drupal\book\BookOutline $book_outline */
$book_outline = \Drupal::service('book.outline');
if ($book_link['nid']) {
$variables['tree'] = book_children($book_link);
$variables['tree'] = $book_outline->childrenLinks($book_link);
$build = array();
if ($prev = book_prev($book_link)) {
if ($prev = $book_outline->prevLink($book_link)) {
$prev_href = \Drupal::url('node.view', array('node' => $prev['nid']));
$build['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'prev',
......@@ -611,7 +439,7 @@ function template_preprocess_book_navigation(&$variables) {
$variables['parent_title'] = String::checkPlain($parent['title']);
}
if ($next = book_next($book_link)) {
if ($next = $book_outline->nextLink($book_link)) {
$next_href = \Drupal::url('node.view', array('node' => $next['nid']));
$build['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'next',
......
......@@ -7,9 +7,12 @@ services:
book.manager:
class: Drupal\book\BookManager
arguments: ['@database', '@entity.manager', '@string_translation', '@config.factory']
book.outline:
class: Drupal\book\BookOutline
arguments: ['@book.manager']
book.export:
class: Drupal\book\BookExport
arguments: ['@entity.manager']
arguments: ['@entity.manager', '@book.manager']
access_check.book.removable:
class: Drupal\book\Access\BookNodeIsRemovableAccessCheck
......
......@@ -31,15 +31,25 @@ class BookExport {
*/
protected $viewBuilder;
/**
* The book manager.
*
* @var \Drupal\book\BookManagerInterface
*/
protected $bookManager;
/**
* Constructs a BookExport object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entityManager
* The entity manager.
* @param \Drupal\book\BookManagerInterface $book_manager
* The book manager.
*/
public function __construct(EntityManagerInterface $entityManager) {
public function __construct(EntityManagerInterface $entityManager, BookManagerInterface $book_manager) {
$this->nodeStorage = $entityManager->getStorage('node');
$this->viewBuilder = $entityManager->getViewBuilder('node');
$this->bookManager = $book_manager;
}
/**
......@@ -67,7 +77,7 @@ public function bookExportHtml(NodeInterface $node) {
throw new \Exception();
}
$tree = \Drupal::service('book.manager')->bookMenuSubtreeData($node->book);
$tree = $this->bookManager->bookSubtreeData($node->book);
$contents = $this->exportTraverse($tree, array($this, 'bookNodeExport'));
return array(
'#theme' => 'book_export_html',
......
......@@ -53,6 +53,20 @@ public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL);
*/
public function loadBookLink($nid, $translate = TRUE);
/**
* Loads multiple book entries.
*
* @param int[] $nids
* An array of nids to load.
*
* @param bool $translate
* If TRUE, set access, title, and other elements.
*
* @return array[]
* The book data of each node keyed by NID.
*/
public function loadBookLinks($nids, $translate = TRUE);
/**
* Returns an array of book pages in table of contents order.
*
......@@ -94,10 +108,7 @@ public function getParentDepthLimit(array $book_link);
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).
* Provides book loading, access control and translation.
*
* @param array $link
* A book link.
......@@ -107,6 +118,21 @@ public function bookTreeCollectNodeLinks(&$tree, &$node_links);
*/
public function bookLinkTranslate(&$link);
/**
* Gets the book for a page and returns it as a linear array.
*
* @param array $book_link
* A fully loaded book link that is part of the book hierarchy.
*
* @return array
* A linear array of book links in the order that the links are shown in the
* book, so the previous and next pages are the elements before and after the
* element corresponding to the current node. The children of the current node
* (if any) will come immediately after it in the array, and links will only
* be fetched as deep as one level deeper than $book_link.
*/
public function bookTreeGetFlat(array $book_link);
/**
* Returns an array of all books.
*
......@@ -195,7 +221,7 @@ public function deleteFromBook($nid);
* - leaf: The menu item has no submenu.
*
* @param array $tree
* A data structure representing the tree as returned from menu_tree_data.
* A data structure representing the tree as returned from buildBookOutlineData.
*
* @return array
* A structured array to be rendered by drupal_render().
......@@ -224,12 +250,12 @@ public function bookTreeCheckAccess(&$tree, $node_links = array());
* tree.
*
* @param array $link
* A fully loaded menu link.
* A fully loaded book link.
*
* @return
* A subtree of menu links in an array, in the order they should be rendered.
* A subtree of book links in an array, in the order they should be rendered.
*/
public function bookMenuSubtreeData($link);
public function bookSubtreeData($link);
/**
* Determines if a node can be removed from the book.
......
<?php
/**
* @file
* Contains \Drupal\book\BookOutline.
*/
namespace Drupal\book;
/**
* Provides handling to render the book outline.
*/
class BookOutline {
/**
* The book manager.
*
* @var \Drupal\book\BookManagerInterface
*/
protected $bookManager;
/**
* Constructs a new BookOutline.
*
* @param \Drupal\book\BookManagerInterface $book_manager
* The book manager.
*/
public function __construct(BookManagerInterface $book_manager) {
$this->bookManager = $book_manager;
}
/**
* Fetches the book link for the previous page of the book.
*
* @param array $book_link
* A fully loaded book link that is part of the book hierarchy.
*
* @return array
* A fully loaded book link for the page before the one represented in
* $book_link.
*/
public function prevLink(array $book_link) {
// If the parent is zero, we are at the start of a book.
if ($book_link['pid'] == 0) {
return NULL;
}
// Assigning the array to $flat resets the array pointer for use with each().
$flat = $this->bookManager->bookTreeGetFlat($book_link);
$curr = NULL;
do {
$prev = $curr;
list($key, $curr) = each($flat);
} while ($key && $key != $book_link['nid']);
if ($key == $book_link['nid']) {
// The previous page in the book may be a child of the previous visible link.
if ($prev['depth'] == $book_link['depth']) {
// The subtree will have only one link at the top level - get its data.
$tree = $this->bookManager->bookSubtreeData($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']);
}
$this->bookManager->bookLinkTranslate($data['link']);
return $data['link'];
}
else {
$this->bookManager->bookLinkTranslate($prev);
return $prev;
}
}
}
/**
* Fetches the book link for the next page of the book.
*
* @param array $book_link
* A fully loaded book link that is part of the book hierarchy.
*
* @return array
* A fully loaded book link for the page after the one represented in
* $book_link.
*/
public function nextLink(array $book_link) {
// Assigning the array to $flat resets the array pointer for use with each().
$flat = $this->bookManager->bookTreeGetFlat($book_link);
do {
list($key,) = each($flat);
} while ($key && $key != $book_link['nid']);
if ($key == $book_link['nid']) {
$next = current($flat);
if ($next) {
$this->bookManager->bookLinkTranslate($next);
}
return $next;
}
}
/**
* Formats the book links for the child pages of the current page.
*
* @param array $book_link
* A fully loaded book link that is part of the book hierarchy.
*
* @return array
* HTML for the links to the child pages of the current page.
*/
public function childrenLinks(array $book_link) {
$flat = $this->bookManager->bookTreeGetFlat($book_link);
$children = array();
if ($book_link['has_children']) {
// Walk through the array until we find the current page.
do {
$link = array_shift($flat);
} 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['pid'] == $book_link['nid']) {
$data['link'] = $link;
$data['below'] = '';
$children[] = $data;
}
}
if ($children) {
$elements = $this->bookManager->bookTreeOutput($children);
return drupal_render($elements);
}
return '';
}
}
......@@ -142,7 +142,7 @@ protected function bookAdminTable(NodeInterface $node, array &$form) {
'#tree' => TRUE,
);
$tree = \Drupal::service('book.manager')->bookMenuSubtreeData($node->book);
$tree = $this->bookManager->bookSubtreeData($node->book);
// Do not include the book item itself.
$tree = array_shift($tree);
if ($tree['below']) {
......
......@@ -8,6 +8,7 @@
namespace Drupal\book\Plugin\Block;
use Drupal\block\BlockBase;
use Drupal\book\BookManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
......@@ -30,6 +31,13 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt
*/
protected $request;
/**
* The book manager.
*
* @var \Drupal\book\BookManagerInterface
*/
protected $bookManager;
/**
* Constructs a new BookNavigationBlock instance.
*
......@@ -41,11 +49,14 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt
* The plugin implementation definition.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\book\BookManagerInterface $book_manager
* The book manager.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, Request $request) {
public function __construct(array $configuration, $plugin_id, array $plugin_definition, Request $request, BookManagerInterface $book_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->request = $request;
$this->bookManager = $book_manager;
}
/**
......@@ -56,7 +67,8 @@ public static function create(ContainerInterface $container, array $configuratio
$configuration,
$plugin_id,
$plugin_definition,
$container->get('request')
$container->get('request'),
$container->get('book.manager')
);
}
......@@ -107,12 +119,12 @@ public function build() {
if ($this->configuration['block_mode'] == 'all pages') {
$book_menus = array();
$pseudo_tree = array(0 => array('below' => FALSE));
foreach (book_get_books() as $book_id => $book) {
foreach ($this->bookManager->getAllBooks() as $book_id => $book) {
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['bid'], $node->book);
$book_menus[$book_id] = \Drupal::service('book.manager')->bookTreeOutput($data);
$data = $this->bookManager->bookTreeAllData($node->book['bid'], $node->book);
$book_menus[$book_id] = $this->bookManager->bookTreeOutput($data);
}
else {
// Since we know we will only display a link to the top node, there
......@@ -122,7 +134,7 @@ public function build() {
$book_node = node_load($book['nid']);
$book['access'] = $book_node->access('view');
$pseudo_tree[0]['link'] = $book;
$book_menus[$book_id] = \Drupal::service('book.manager')->bookTreeOutput($pseudo_tree);
$book_menus[$book_id] = $this->bookManager->bookTreeOutput($pseudo_tree);
}
}
if ($book_menus) {
......@@ -140,10 +152,10 @@ 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['bid'], $node->book);
$tree = $this->bookManager->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']);
$below = $this->bookManager->bookTreeOutput($data['below']);
if (!empty($below)) {
return $below;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment