Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Access\BookNodeIsRemovableAccessCheck.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Access;
|
||||
|
||||
use Drupal\book\BookManagerInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Determines whether the requested node can be removed from its book.
|
||||
*/
|
||||
class BookNodeIsRemovableAccessCheck implements AccessInterface{
|
||||
|
||||
/**
|
||||
* Book Manager Service.
|
||||
*
|
||||
* @var \Drupal\book\BookManagerInterface
|
||||
*/
|
||||
protected $bookManager;
|
||||
|
||||
/**
|
||||
* Constructs a BookNodeIsRemovableAccessCheck object.
|
||||
*
|
||||
* @param \Drupal\book\BookManagerInterface $book_manager
|
||||
* Book Manager Service.
|
||||
*/
|
||||
public function __construct(BookManagerInterface $book_manager) {
|
||||
$this->bookManager = $book_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for removing the node from its book.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node requested to be removed from its book.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(NodeInterface $node) {
|
||||
return AccessResult::allowedIf($this->bookManager->checkNodeIsRemovable($node))->cacheUntilEntityChanges($node);
|
||||
}
|
||||
|
||||
}
|
98
core/modules/book/src/BookBreadcrumbBuilder.php
Normal file
98
core/modules/book/src/BookBreadcrumbBuilder.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\BookBreadcrumbBuilder.
|
||||
*/
|
||||
|
||||
namespace Drupal\book;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Provides a breadcrumb builder for nodes in a book.
|
||||
*/
|
||||
class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The node storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The current user account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Constructs the BookBreadcrumbBuilder.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user account.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager, AccessManagerInterface $access_manager, AccountInterface $account) {
|
||||
$this->nodeStorage = $entity_manager->getStorage('node');
|
||||
$this->accessManager = $access_manager;
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(RouteMatchInterface $route_match) {
|
||||
$node = $route_match->getParameter('node');
|
||||
return $node instanceof NodeInterface && !empty($node->book);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(RouteMatchInterface $route_match) {
|
||||
$book_nids = array();
|
||||
$links = array(Link::createFromRoute($this->t('Home'), '<front>'));
|
||||
$book = $route_match->getParameter('node')->book;
|
||||
$depth = 1;
|
||||
// We skip the current node.
|
||||
while (!empty($book['p' . ($depth + 1)])) {
|
||||
$book_nids[] = $book['p' . $depth];
|
||||
$depth++;
|
||||
}
|
||||
$parent_books = $this->nodeStorage->loadMultiple($book_nids);
|
||||
if (count($parent_books) > 0) {
|
||||
$depth = 1;
|
||||
while (!empty($book['p' . ($depth + 1)])) {
|
||||
if (!empty($parent_books[$book['p' . $depth]]) && ($parent_book = $parent_books[$book['p' . $depth]])) {
|
||||
if ($parent_book->access('view', $this->account)) {
|
||||
$links[] = Link::createFromRoute($parent_book->label(), 'entity.node.canonical', array('node' => $parent_book->id()));
|
||||
}
|
||||
}
|
||||
$depth++;
|
||||
}
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
|
||||
}
|
151
core/modules/book/src/BookExport.php
Normal file
151
core/modules/book/src/BookExport.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\BookExport.
|
||||
*/
|
||||
|
||||
namespace Drupal\book;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Provides methods for exporting book to different formats.
|
||||
*
|
||||
* If you would like to add another format, swap this class in container.
|
||||
*/
|
||||
class BookExport {
|
||||
|
||||
/**
|
||||
* The node storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
/**
|
||||
* The node view builder.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityViewBuilderInterface
|
||||
*/
|
||||
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, BookManagerInterface $book_manager) {
|
||||
$this->nodeStorage = $entityManager->getStorage('node');
|
||||
$this->viewBuilder = $entityManager->getViewBuilder('node');
|
||||
$this->bookManager = $book_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for export when invoked by book_export().
|
||||
*
|
||||
* The given node is embedded to its absolute depth in a top level section. For
|
||||
* example, a child node with depth 2 in the hierarchy is contained in
|
||||
* (otherwise empty) <div> elements corresponding to depth 0 and depth 1.
|
||||
* This is intended to support WYSIWYG output - e.g., level 3 sections always
|
||||
* look like level 3 sections, no matter their depth relative to the node
|
||||
* selected to be exported as printer-friendly HTML.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node to export.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Thrown when the node was not attached to a book.
|
||||
*
|
||||
* @return array
|
||||
* A render array representing the HTML for a node and its children in the
|
||||
* book hierarchy.
|
||||
*/
|
||||
public function bookExportHtml(NodeInterface $node) {
|
||||
if (!isset($node->book)) {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
$tree = $this->bookManager->bookSubtreeData($node->book);
|
||||
$contents = $this->exportTraverse($tree, array($this, 'bookNodeExport'));
|
||||
return array(
|
||||
'#theme' => 'book_export_html',
|
||||
'#title' => $node->label(),
|
||||
'#contents' => $contents,
|
||||
'#depth' => $node->book['depth'],
|
||||
'#cache' => [
|
||||
'tags' => $node->getEntityType()->getListCacheTags(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the book tree to build printable or exportable output.
|
||||
*
|
||||
* During the traversal, the callback is applied to each node and is called
|
||||
* recursively for each child of the node (in weight, title order).
|
||||
*
|
||||
* @param array $tree
|
||||
* A subtree of the book menu hierarchy, rooted at the current page.
|
||||
* @param callable $callable
|
||||
* A callback to be called upon visiting a node in the tree.
|
||||
*
|
||||
* @return string
|
||||
* The output generated in visiting each node.
|
||||
*/
|
||||
protected function exportTraverse(array $tree, $callable) {
|
||||
// If there is no valid callable, use the default callback.
|
||||
$callable = !empty($callable) ? $callable : array($this, 'bookNodeExport');
|
||||
|
||||
$build = array();
|
||||
foreach ($tree as $data) {
|
||||
// Note- access checking is already performed when building the tree.
|
||||
if ($node = $this->nodeStorage->load($data['link']['nid'])) {
|
||||
$children = $data['below'] ? $this->exportTraverse($data['below'], $callable) : '';
|
||||
$build[] = call_user_func($callable, $node, $children);
|
||||
}
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates printer-friendly HTML for a node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node that will be output.
|
||||
* @param string $children
|
||||
* (optional) All the rendered child nodes within the current node. Defaults
|
||||
* to an empty string.
|
||||
*
|
||||
* @return array
|
||||
* A render array for the exported HTML of a given node.
|
||||
*
|
||||
* @see \Drupal\book\BookExport::exportTraverse()
|
||||
*/
|
||||
protected function bookNodeExport(NodeInterface $node, $children = '') {
|
||||
$build = $this->viewBuilder->view($node, 'print', NULL);
|
||||
unset($build['#theme']);
|
||||
|
||||
return array(
|
||||
'#theme' => 'book_node_export_html',
|
||||
'#content' => $build,
|
||||
'#node' => $node,
|
||||
'#children' => $children,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
1092
core/modules/book/src/BookManager.php
Normal file
1092
core/modules/book/src/BookManager.php
Normal file
File diff suppressed because it is too large
Load diff
302
core/modules/book/src/BookManagerInterface.php
Normal file
302
core/modules/book/src/BookManagerInterface.php
Normal file
|
@ -0,0 +1,302 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\BookManagerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\book;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
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);
|
||||
|
||||
/**
|
||||
* Gets the active trail IDs for the specified book at the provided path.
|
||||
*
|
||||
* @param string $bid
|
||||
* The Book ID to find links for.
|
||||
* @param array $link
|
||||
* A fully loaded menu link.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the active trail: a list of mlids.
|
||||
*/
|
||||
public function getActiveTrailIds($bid, $link);
|
||||
|
||||
/**
|
||||
* Loads a single book entry.
|
||||
*
|
||||
* The entries of a book entry is documented in
|
||||
* \Drupal\book\BookOutlineStorageInterface::loadMultiple.
|
||||
*
|
||||
* If $translate is TRUE, it also checks access ('access' key) and
|
||||
* loads the title from the node itself.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @see \Drupal\book\BookOutlineStorageInterface::loadMultiple
|
||||
*/
|
||||
public function loadBookLink($nid, $translate = TRUE);
|
||||
|
||||
/**
|
||||
* Loads multiple book entries.
|
||||
*
|
||||
* The entries of a book entry is documented in
|
||||
* \Drupal\book\BookOutlineStorageInterface::loadMultiple.
|
||||
*
|
||||
* If $translate is TRUE, it also checks access ('access' key) and
|
||||
* loads the title from the node itself.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @see \Drupal\book\BookOutlineStorageInterface::loadMultiple
|
||||
*/
|
||||
public function loadBookLinks($nids, $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 book loading, access control and translation.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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 \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* 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, FormStateInterface $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.
|
||||
*
|
||||
* @param array $tree
|
||||
* A data structure representing the tree as returned from buildBookOutlineData.
|
||||
*
|
||||
* @return array
|
||||
* A structured array to be rendered by drupal_render().
|
||||
*
|
||||
* @see \Drupal\Core\Menu\MenuLinkTree::build
|
||||
*/
|
||||
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 book link.
|
||||
*
|
||||
* @return
|
||||
* A subtree of book links in an array, in the order they should be rendered.
|
||||
*/
|
||||
public function bookSubtreeData($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);
|
||||
|
||||
}
|
134
core/modules/book/src/BookOutline.php
Normal file
134
core/modules/book/src/BookOutline.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?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) {
|
||||
return $this->bookManager->bookTreeOutput($children);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
206
core/modules/book/src/BookOutlineStorage.php
Normal file
206
core/modules/book/src/BookOutlineStorage.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\BookOutlineStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\book;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* Defines a storage class for books outline.
|
||||
*/
|
||||
class BookOutlineStorage implements BookOutlineStorageInterface {
|
||||
|
||||
/**
|
||||
* Database Service Object.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* Constructs a BookOutlineStorage object.
|
||||
*/
|
||||
public function __construct(Connection $connection) {
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBooks() {
|
||||
return $this->connection->query("SELECT DISTINCT(bid) FROM {book}")->fetchCol();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasBooks() {
|
||||
return (bool) $this->connection
|
||||
->query('SELECT count(bid) FROM {book}')
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMultiple($nids, $access = TRUE) {
|
||||
$query = $this->connection->select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC));
|
||||
$query->fields('b');
|
||||
$query->condition('b.nid', $nids, 'IN');
|
||||
|
||||
if ($access) {
|
||||
$query->addTag('node_access');
|
||||
$query->addMetaData('base_table', 'book');
|
||||
}
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChildRelativeDepth($book_link, $max_depth) {
|
||||
$query = $this->connection->select('book');
|
||||
$query->addField('book', 'depth');
|
||||
$query->condition('bid', $book_link['bid']);
|
||||
$query->orderBy('depth', 'DESC');
|
||||
$query->range(0, 1);
|
||||
|
||||
$i = 1;
|
||||
$p = 'p1';
|
||||
while ($i <= $max_depth && $book_link[$p]) {
|
||||
$query->condition($p, $book_link[$p]);
|
||||
$p = 'p' . ++$i;
|
||||
}
|
||||
|
||||
return $query->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($nid) {
|
||||
return $this->connection->delete('book')
|
||||
->condition('nid', $nid)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadBookChildren($pid) {
|
||||
return $this->connection
|
||||
->query("SELECT * FROM {book} WHERE pid = :pid", array(':pid' => $pid))
|
||||
->fetchAllAssoc('nid', \PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBookMenuTree($bid, $parameters, $min_depth, $max_depth) {
|
||||
$query = $this->connection->select('book');
|
||||
$query->fields('book');
|
||||
for ($i = 1; $i <= $max_depth; $i++) {
|
||||
$query->orderBy('p' . $i, 'ASC');
|
||||
}
|
||||
$query->condition('bid', $bid);
|
||||
if (!empty($parameters['expanded'])) {
|
||||
$query->condition('pid', $parameters['expanded'], 'IN');
|
||||
}
|
||||
if ($min_depth != 1) {
|
||||
$query->condition('depth', $min_depth, '>=');
|
||||
}
|
||||
if (isset($parameters['max_depth'])) {
|
||||
$query->condition('depth', $parameters['max_depth'], '<=');
|
||||
}
|
||||
// Add custom query conditions, if any were passed.
|
||||
if (isset($parameters['conditions'])) {
|
||||
foreach ($parameters['conditions'] as $column => $value) {
|
||||
$query->condition($column, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert($link, $parents) {
|
||||
return $this->connection
|
||||
->insert('book')
|
||||
->fields(array(
|
||||
'nid' => $link['nid'],
|
||||
'bid' => $link['bid'],
|
||||
'pid' => $link['pid'],
|
||||
'weight' => $link['weight'],
|
||||
) + $parents
|
||||
)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update($nid, $fields) {
|
||||
return $this->connection
|
||||
->update('book')
|
||||
->fields($fields)
|
||||
->condition('nid', $nid)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateMovedChildren($bid, $original, $expressions, $shift) {
|
||||
$query = $this->connection->update('book');
|
||||
$query->fields(array('bid' => $bid));
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countOriginalLinkChildren($original) {
|
||||
return $this->connection->select('book', 'b')
|
||||
->condition('bid', $original['bid'])
|
||||
->condition('pid', $original['pid'])
|
||||
->condition('nid', $original['nid'], '<>')
|
||||
->countQuery()
|
||||
->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBookSubtree($link, $max_depth) {
|
||||
$query = db_select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC));
|
||||
$query->fields('b');
|
||||
$query->condition('b.bid', $link['bid']);
|
||||
|
||||
for ($i = 1; $i <= $max_depth && $link["p$i"]; ++$i) {
|
||||
$query->condition("p$i", $link["p$i"]);
|
||||
}
|
||||
for ($i = 1; $i <= $max_depth; ++$i) {
|
||||
$query->orderBy("p$i");
|
||||
}
|
||||
return $query->execute();
|
||||
}
|
||||
}
|
174
core/modules/book/src/BookOutlineStorageInterface.php
Normal file
174
core/modules/book/src/BookOutlineStorageInterface.php
Normal file
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\BookOutlineStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\book;
|
||||
|
||||
/**
|
||||
* Defines a common interface for book outline storage classes.
|
||||
*/
|
||||
interface BookOutlineStorageInterface {
|
||||
|
||||
/**
|
||||
* Gets books (the highest positioned book links).
|
||||
*
|
||||
* @return array
|
||||
* An array of book IDs.
|
||||
*/
|
||||
public function getBooks();
|
||||
|
||||
/**
|
||||
* Checks if there are any books.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are books, FALSE if not.
|
||||
*/
|
||||
public function hasBooks();
|
||||
|
||||
/**
|
||||
* Loads books.
|
||||
*
|
||||
* Each book entry consists of the following keys:
|
||||
* - bid: The node ID of the main book.
|
||||
* - nid: The node ID of the book entry itself.
|
||||
* - pid: The parent node ID of the book.
|
||||
* - has_children: A boolean to indicate whether the book has children.
|
||||
* - weight: The weight of the book entry to order siblings.
|
||||
* - depth: The depth in the menu hierarchy the entry is placed into.
|
||||
*
|
||||
* @param array $nids
|
||||
* An array of node IDs.
|
||||
* @param bool $access
|
||||
* Whether access checking should be taken into account.
|
||||
*
|
||||
* @return array
|
||||
* Array of loaded book items.
|
||||
*/
|
||||
public function loadMultiple($nids, $access = TRUE);
|
||||
|
||||
/**
|
||||
* Gets child relative depth.
|
||||
*
|
||||
* @param array $book_link
|
||||
* The book link.
|
||||
*
|
||||
* @param int $max_depth
|
||||
* The maximum supported depth of the book tree.
|
||||
*
|
||||
* @return int
|
||||
* The depth of the searched book.
|
||||
*/
|
||||
public function getChildRelativeDepth($book_link, $max_depth);
|
||||
|
||||
/**
|
||||
* Deletes a book entry.
|
||||
*
|
||||
* @param int $nid
|
||||
* Deletes a book entry.
|
||||
*
|
||||
* @return mixed
|
||||
* Number of deleted book entries.
|
||||
*/
|
||||
public function delete($nid);
|
||||
|
||||
/**
|
||||
* Loads book's children using it's parent ID.
|
||||
*
|
||||
* @param int $pid
|
||||
* The book's parent ID.
|
||||
*
|
||||
* @return array
|
||||
* Array of loaded book items.
|
||||
*/
|
||||
public function loadBookChildren($pid);
|
||||
|
||||
/**
|
||||
* Builds tree data used for the menu tree.
|
||||
*
|
||||
* @param int $bid
|
||||
* The ID of the book that we are building the tree for.
|
||||
* @param array $parameters
|
||||
* An associative array of build parameters. For info about individual
|
||||
* parameters see BookManager::bookTreeBuild().
|
||||
* @param int $min_depth
|
||||
* The minimum depth of book links in the resulting tree.
|
||||
* @param int $max_depth
|
||||
* The maximum supported depth of the book tree.
|
||||
*
|
||||
* @return array
|
||||
* Array of loaded book links.
|
||||
*/
|
||||
public function getBookMenuTree($bid, $parameters, $min_depth, $max_depth);
|
||||
|
||||
/**
|
||||
* Inserts a book link.
|
||||
*
|
||||
* @param array $link
|
||||
* The link array to be inserted in the database.
|
||||
* @param array $parents
|
||||
* The array of parent ids for the link to be inserted.
|
||||
*
|
||||
* @return mixed
|
||||
* The last insert ID of the query, if one exists.
|
||||
*/
|
||||
public function insert($link, $parents);
|
||||
|
||||
|
||||
/**
|
||||
* Updates book reference for links that were moved between books.
|
||||
*
|
||||
* @param int $nid
|
||||
* The nid of the book entry to be updated.
|
||||
* @param array $fields
|
||||
* The array of fields to be updated.
|
||||
*
|
||||
* @return mixed
|
||||
* The number of rows matched by the update query.
|
||||
*/
|
||||
public function update($nid, $fields);
|
||||
|
||||
/**
|
||||
* Update the book ID of the book link that it's being moved.
|
||||
*
|
||||
* @param int $bid
|
||||
* The ID of the book whose children we move.
|
||||
* @param array $original
|
||||
* The original parent of the book link.
|
||||
* @param array $expressions
|
||||
* Array of expressions to be added to the query.
|
||||
* @param int $shift
|
||||
* The difference in depth between the old and the new position of the
|
||||
* element being moved.
|
||||
*
|
||||
* @return mixed
|
||||
* The number of rows matched by the update query.
|
||||
*/
|
||||
public function updateMovedChildren($bid, $original, $expressions, $shift);
|
||||
|
||||
/**
|
||||
* Count the number of original link children.
|
||||
*
|
||||
* @param array $original
|
||||
* The book link array.
|
||||
*
|
||||
* @return int
|
||||
* Number of children.
|
||||
*/
|
||||
public function countOriginalLinkChildren($original);
|
||||
|
||||
/**
|
||||
* Get book subtree.
|
||||
*
|
||||
* @param array $link
|
||||
* A fully loaded book link.
|
||||
* @param int $max_depth
|
||||
* The maximum supported depth of the book tree.
|
||||
*
|
||||
* @return array
|
||||
* Array of unordered subtree book items.
|
||||
*/
|
||||
public function getBookSubtree($link, $max_depth);
|
||||
}
|
98
core/modules/book/src/BookUninstallValidator.php
Normal file
98
core/modules/book/src/BookUninstallValidator.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\BookUninstallValidator.
|
||||
*/
|
||||
|
||||
namespace Drupal\book;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
|
||||
/**
|
||||
* Prevents book module from being uninstalled whilst any book nodes exist or
|
||||
* there are any book outline stored.
|
||||
*/
|
||||
class BookUninstallValidator implements ModuleUninstallValidatorInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The book outline storage.
|
||||
*
|
||||
* @var \Drupal\book\BookOutlineStorageInterface
|
||||
*/
|
||||
protected $bookOutlineStorage;
|
||||
|
||||
/**
|
||||
* The entity query for node.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
protected $entityQuery;
|
||||
|
||||
/**
|
||||
* Constructs a new BookUninstallValidator.
|
||||
*
|
||||
* @param \Drupal\book\BookOutlineStorageInterface $book_outline_storage
|
||||
* The book outline storage.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* The entity query factory.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
*/
|
||||
public function __construct(BookOutlineStorageInterface $book_outline_storage, QueryFactory $query_factory, TranslationInterface $string_translation) {
|
||||
$this->bookOutlineStorage = $book_outline_storage;
|
||||
$this->entityQuery = $query_factory->get('node');
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($module) {
|
||||
$reasons = [];
|
||||
if ($module == 'book') {
|
||||
if ($this->hasBookOutlines()) {
|
||||
$reasons[] = $this->t('To uninstall Book, delete all content that is part of a book');
|
||||
}
|
||||
else {
|
||||
// The book node type is provided by the Book module. Prevent uninstall
|
||||
// if there are any nodes of that type.
|
||||
if ($this->hasBookNodes()) {
|
||||
$reasons[] = $this->t('To uninstall Book, delete all content that has the Book content type');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $reasons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any books in an outline.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are books, FALSE if not.
|
||||
*/
|
||||
protected function hasBookOutlines() {
|
||||
return $this->bookOutlineStorage->hasBooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is any book nodes or not.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are book nodes, FALSE otherwise.
|
||||
*/
|
||||
protected function hasBookNodes() {
|
||||
$nodes = $this->entityQuery
|
||||
->condition('type', 'book')
|
||||
->accessCheck(FALSE)
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
return !empty($nodes);
|
||||
}
|
||||
|
||||
}
|
71
core/modules/book/src/Cache/BookNavigationCacheContext.php
Normal file
71
core/modules/book/src/Cache/BookNavigationCacheContext.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Cache\BookNavigationCacheContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Cache;
|
||||
|
||||
use Drupal\Core\Cache\Context\CacheContextInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Defines the book navigation cache context service.
|
||||
*
|
||||
* This allows for book navigation location-aware caching. It depends on:
|
||||
* - whether the current route represents a book node at all
|
||||
* - and if so, where in the book hierarchy we are
|
||||
*
|
||||
* This class is container-aware to avoid initializing the 'book.manager'
|
||||
* service when it is not necessary.
|
||||
*/
|
||||
class BookNavigationCacheContext extends ContainerAware implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* Constructs a new BookNavigationCacheContext service.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(RequestStack $request_stack) {
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getLabel() {
|
||||
return t("Book navigation");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
// Find the current book's ID.
|
||||
$current_bid = 0;
|
||||
if ($node = $this->requestStack->getCurrentRequest()->get('node')) {
|
||||
$current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
|
||||
}
|
||||
|
||||
// If we're not looking at a book node, then we're not navigating a book.
|
||||
if ($current_bid === 0) {
|
||||
return 'book.none';
|
||||
}
|
||||
|
||||
// If we're looking at a book node, get the trail for that node.
|
||||
$active_trail = $this->container->get('book.manager')
|
||||
->getActiveTrailIds($node->book['bid'], $node->book);
|
||||
return 'book.' . implode('|', $active_trail);
|
||||
}
|
||||
|
||||
}
|
172
core/modules/book/src/Controller/BookController.php
Normal file
172
core/modules/book/src/Controller/BookController.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Controller\BookController.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Controller;
|
||||
|
||||
use Drupal\book\BookExport;
|
||||
use Drupal\book\BookManagerInterface;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Controller routines for book routes.
|
||||
*/
|
||||
class BookController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The book manager.
|
||||
*
|
||||
* @var \Drupal\book\BookManagerInterface
|
||||
*/
|
||||
protected $bookManager;
|
||||
|
||||
/**
|
||||
* The book export service.
|
||||
*
|
||||
* @var \Drupal\book\BookExport
|
||||
*/
|
||||
protected $bookExport;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a BookController object.
|
||||
*
|
||||
* @param \Drupal\book\BookManagerInterface $bookManager
|
||||
* The book manager.
|
||||
* @param \Drupal\book\BookExport $bookExport
|
||||
* The book export service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(BookManagerInterface $bookManager, BookExport $bookExport, RendererInterface $renderer) {
|
||||
$this->bookManager = $bookManager;
|
||||
$this->bookExport = $bookExport;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('book.manager'),
|
||||
$container->get('book.export'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an administrative overview of all books.
|
||||
*
|
||||
* @return array
|
||||
* A render array representing the administrative page content.
|
||||
*
|
||||
*/
|
||||
public function adminOverview() {
|
||||
$rows = array();
|
||||
|
||||
$headers = array(t('Book'), t('Operations'));
|
||||
// Add any recognized books to the table list.
|
||||
foreach ($this->bookManager->getAllBooks() as $book) {
|
||||
/** @var \Drupal\Core\Url $url */
|
||||
$url = $book['url'];
|
||||
if (isset($book['options'])) {
|
||||
$url->setOptions($book['options']);
|
||||
}
|
||||
$row = array(
|
||||
$this->l($book['title'], $url),
|
||||
);
|
||||
$links = array();
|
||||
$links['edit'] = array(
|
||||
'title' => t('Edit order and titles'),
|
||||
'url' => Url::fromRoute('book.admin_edit', ['node' => $book['nid']]),
|
||||
);
|
||||
$row[] = array(
|
||||
'data' => array(
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
),
|
||||
);
|
||||
$rows[] = $row;
|
||||
}
|
||||
return array(
|
||||
'#type' => 'table',
|
||||
'#header' => $headers,
|
||||
'#rows' => $rows,
|
||||
'#empty' => t('No books available.'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a listing of all books.
|
||||
*
|
||||
* @return array
|
||||
* A render array representing the listing of all books content.
|
||||
*/
|
||||
public function bookRender() {
|
||||
$book_list = array();
|
||||
foreach ($this->bookManager->getAllBooks() as $book) {
|
||||
$book_list[] = $this->l($book['title'], $book['url']);
|
||||
}
|
||||
return array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $book_list,
|
||||
'#cache' => [
|
||||
'tags' => \Drupal::entityManager()->getDefinition('node')->getListCacheTags(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates representations of a book page and its children.
|
||||
*
|
||||
* The method delegates the generation of output to helper methods. The method
|
||||
* name is derived by prepending 'bookExport' to the camelized form of given
|
||||
* output type. For example, a type of 'html' results in a call to the method
|
||||
* bookExportHtml().
|
||||
*
|
||||
* @param string $type
|
||||
* A string encoding the type of output requested. The following types are
|
||||
* currently supported in book module:
|
||||
* - html: Printer-friendly HTML.
|
||||
* Other types may be supported in contributed modules.
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node to export.
|
||||
*
|
||||
* @return array
|
||||
* A render array representing the node and its children in the book
|
||||
* hierarchy in a format determined by the $type parameter.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function bookExport($type, NodeInterface $node) {
|
||||
$method = 'bookExport' . Container::camelize($type);
|
||||
|
||||
// @todo Convert the custom export functionality to serializer.
|
||||
if (!method_exists($this->bookExport, $method)) {
|
||||
drupal_set_message(t('Unknown export format.'));
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$exported_book = $this->bookExport->{$method}($node);
|
||||
return new Response($this->renderer->renderRoot($exported_book));
|
||||
}
|
||||
|
||||
}
|
300
core/modules/book/src/Form/BookAdminEditForm.php
Normal file
300
core/modules/book/src/Form/BookAdminEditForm.php
Normal file
|
@ -0,0 +1,300 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Form\BookAdminEditForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Form;
|
||||
|
||||
use Drupal\book\BookManager;
|
||||
use Drupal\book\BookManagerInterface;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for administering a single book's hierarchy.
|
||||
*/
|
||||
class BookAdminEditForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The node storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
/**
|
||||
* The book manager.
|
||||
*
|
||||
* @var \Drupal\book\BookManagerInterface
|
||||
*/
|
||||
protected $bookManager;
|
||||
|
||||
/**
|
||||
* Constructs a new BookAdminEditForm.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
|
||||
* The custom block storage.
|
||||
* @param \Drupal\book\BookManagerInterface $book_manager
|
||||
* The book manager.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $node_storage, BookManagerInterface $book_manager) {
|
||||
$this->nodeStorage = $node_storage;
|
||||
$this->bookManager = $book_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
$entity_manager = $container->get('entity.manager');
|
||||
return new static(
|
||||
$entity_manager->getStorage('node'),
|
||||
$container->get('book.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'book_admin_edit';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $node = NULL) {
|
||||
$form['#title'] = $node->label();
|
||||
$form['#node'] = $node;
|
||||
$this->bookAdminTable($node, $form);
|
||||
$form['save'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save book pages'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->getValue('tree_hash') != $form_state->getValue('tree_current_hash')) {
|
||||
$form_state->setErrorByName('', $this->t('This book has been modified by another user, the changes could not be saved.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Save elements in the same order as defined in post rather than the form.
|
||||
// This ensures parents are updated before their children, preventing orphans.
|
||||
$user_input = $form_state->getUserInput();
|
||||
if (isset($user_input['table'])) {
|
||||
$order = array_flip(array_keys($user_input['table']));
|
||||
$form['table'] = array_merge($order, $form['table']);
|
||||
|
||||
foreach (Element::children($form['table']) as $key) {
|
||||
if ($form['table'][$key]['#item']) {
|
||||
$row = $form['table'][$key];
|
||||
$values = $form_state->getValue(array('table', $key));
|
||||
|
||||
// Update menu item if moved.
|
||||
if ($row['parent']['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);
|
||||
}
|
||||
|
||||
// Update the title if changed.
|
||||
if ($row['title']['#default_value'] != $values['title']) {
|
||||
$node = $this->nodeStorage->load($values['nid']);
|
||||
$node->revision_log = $this->t('Title changed from %original to %current.', array('%original' => $node->label(), '%current' => $values['title']));
|
||||
$node->title = $values['title'];
|
||||
$node->book['link_title'] = $values['title'];
|
||||
$node->setNewRevision();
|
||||
$node->save();
|
||||
$this->logger('content')->notice('book: updated %title.', array('%title' => $node->label(), 'link' => $node->link($this->t('View'))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drupal_set_message($this->t('Updated book %title.', array('%title' => $form['#node']->label())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the table portion of the form for the book administration page.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node of the top-level page in the book.
|
||||
* @param array $form
|
||||
* The form that is being modified, passed by reference.
|
||||
*
|
||||
* @see self::buildForm()
|
||||
*/
|
||||
protected function bookAdminTable(NodeInterface $node, array &$form) {
|
||||
$form['table'] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => [
|
||||
$this->t('Title'),
|
||||
$this->t('Weight'),
|
||||
$this->t('Parent'),
|
||||
$this->t('Operations'),
|
||||
],
|
||||
'#empty' => $this->t('No book content available.'),
|
||||
'#tabledrag' => [
|
||||
[
|
||||
'action' => 'match',
|
||||
'relationship' => 'parent',
|
||||
'group' => 'book-pid',
|
||||
'subgroup' => 'book-pid',
|
||||
'source' => 'book-nid',
|
||||
'hidden' => TRUE,
|
||||
'limit' => BookManager::BOOK_MAX_DEPTH - 2,
|
||||
],
|
||||
[
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'book-weight',
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
$tree = $this->bookManager->bookSubtreeData($node->book);
|
||||
// Do not include the book item itself.
|
||||
$tree = array_shift($tree);
|
||||
if ($tree['below']) {
|
||||
$hash = Crypt::hashBase64(serialize($tree['below']));
|
||||
// Store the hash value as a hidden form element so that we can detect
|
||||
// if another user changed the book hierarchy.
|
||||
$form['tree_hash'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => $hash,
|
||||
);
|
||||
$form['tree_current_hash'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $hash,
|
||||
);
|
||||
$this->bookAdminTableTree($tree['below'], $form['table']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps build the main table in the book administration page form.
|
||||
*
|
||||
* @param array $tree
|
||||
* A subtree of the book menu hierarchy.
|
||||
* @param array $form
|
||||
* The form that is being modified, passed by reference.
|
||||
*
|
||||
* @see self::buildForm()
|
||||
*/
|
||||
protected function bookAdminTableTree(array $tree, array &$form) {
|
||||
// The delta must be big enough to give each node a distinct value.
|
||||
$count = count($tree);
|
||||
$delta = ($count < 30) ? 15 : intval($count / 2) + 1;
|
||||
|
||||
$access = \Drupal::currentUser()->hasPermission('administer nodes');
|
||||
$destination = $this->getDestinationArray();
|
||||
|
||||
foreach ($tree as $data) {
|
||||
$nid = $data['link']['nid'];
|
||||
$id = 'book-admin-' . $nid;
|
||||
|
||||
$form[$id]['#item'] = $data['link'];
|
||||
$form[$id]['#nid'] = $nid;
|
||||
$form[$id]['#attributes']['class'][] = 'draggable';
|
||||
$form[$id]['#weight'] = $data['link']['weight'];
|
||||
|
||||
if (isset($data['link']['depth']) && $data['link']['depth'] > 2) {
|
||||
$indentation = [
|
||||
'#theme' => 'indentation',
|
||||
'#size' => $data['link']['depth'] - 2,
|
||||
];
|
||||
}
|
||||
|
||||
$form[$id]['title'] = [
|
||||
'#prefix' => !empty($indentation) ? drupal_render($indentation) : '',
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $data['link']['title'],
|
||||
'#maxlength' => 255,
|
||||
'#size' => 40,
|
||||
];
|
||||
|
||||
$form[$id]['weight'] = [
|
||||
'#type' => 'weight',
|
||||
'#default_value' => $data['link']['weight'],
|
||||
'#delta' => max($delta, abs($data['link']['weight'])),
|
||||
'#title' => $this->t('Weight for @title', ['@title' => $data['link']['title']]),
|
||||
'#title_display' => 'invisible',
|
||||
'#attributes' => [
|
||||
'class' => ['book-weight'],
|
||||
],
|
||||
];
|
||||
|
||||
$form[$id]['parent']['nid'] = [
|
||||
'#parents' => ['table', $id, 'nid'],
|
||||
'#type' => 'hidden',
|
||||
'#value' => $nid,
|
||||
'#attributes' => [
|
||||
'class' => ['book-nid'],
|
||||
],
|
||||
];
|
||||
|
||||
$form[$id]['parent']['pid'] = [
|
||||
'#parents' => ['table', $id, 'pid'],
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => $data['link']['pid'],
|
||||
'#attributes' => [
|
||||
'class' => ['book-pid'],
|
||||
],
|
||||
];
|
||||
|
||||
$form[$id]['parent']['bid'] = [
|
||||
'#parents' => ['table', $id, 'bid'],
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => $data['link']['bid'],
|
||||
'#attributes' => [
|
||||
'class' => ['book-bid'],
|
||||
],
|
||||
];
|
||||
|
||||
$form[$id]['operations'] = [
|
||||
'#type' => 'operations',
|
||||
];
|
||||
$form[$id]['operations']['#links']['view'] = [
|
||||
'title' => $this->t('View'),
|
||||
'url' => new Url('entity.node.canonical', ['node' => $nid]),
|
||||
];
|
||||
|
||||
if ($access) {
|
||||
$form[$id]['operations']['#links']['edit'] = [
|
||||
'title' => $this->t('Edit'),
|
||||
'url' => new Url('entity.node.edit_form', ['node' => $nid]),
|
||||
'query' => $destination,
|
||||
];
|
||||
$form[$id]['operations']['#links']['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => new Url('entity.node.delete_form', ['node' => $nid]),
|
||||
'query' => $destination,
|
||||
];
|
||||
}
|
||||
|
||||
if ($data['below']) {
|
||||
$this->bookAdminTableTree($data['below'], $form);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
136
core/modules/book/src/Form/BookOutlineForm.php
Normal file
136
core/modules/book/src/Form/BookOutlineForm.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Form\BookOutlineForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Form;
|
||||
|
||||
use Drupal\book\BookManagerInterface;
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Displays the book outline form.
|
||||
*/
|
||||
class BookOutlineForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* The book being displayed.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* BookManager service.
|
||||
*
|
||||
* @var \Drupal\book\BookManagerInterface
|
||||
*/
|
||||
protected $bookManager;
|
||||
|
||||
/**
|
||||
* Constructs a BookOutlineForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\book\BookManagerInterface $book_manager
|
||||
* The BookManager service.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager, BookManagerInterface $book_manager) {
|
||||
parent::__construct($entity_manager);
|
||||
$this->bookManager = $book_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager'),
|
||||
$container->get('book.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBaseFormId() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form['#title'] = $this->entity->label();
|
||||
|
||||
if (!isset($this->entity->book)) {
|
||||
// The node is not part of any book yet - set default options.
|
||||
$this->entity->book = $this->bookManager->getLinkDefaults($this->entity->id());
|
||||
}
|
||||
else {
|
||||
$this->entity->book['original_bid'] = $this->entity->book['bid'];
|
||||
}
|
||||
|
||||
// Find the depth limit for the parent select.
|
||||
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(), FALSE);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = $this->entity->book['original_bid'] ? $this->t('Update book outline') : $this->t('Add to book outline');
|
||||
$actions['delete']['#value'] = $this->t('Remove from book outline');
|
||||
$actions['delete']['#access'] = $this->bookManager->checkNodeIsRemovable($this->entity);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect(
|
||||
'entity.node.canonical',
|
||||
array('node' => $this->entity->id())
|
||||
);
|
||||
$book_link = $form_state->getValue('book');
|
||||
if (!$book_link['bid']) {
|
||||
drupal_set_message($this->t('No changes were made'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->entity->book = $book_link;
|
||||
if ($this->bookManager->updateOutline($this->entity)) {
|
||||
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->setRedirectUrl($this->entity->urlInfo('book-outline-form'));
|
||||
}
|
||||
else {
|
||||
drupal_set_message($this->t('The book outline has been updated.'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
drupal_set_message($this->t('There was an error adding the post to the book.'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(array $form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirectUrl($this->entity->urlInfo('book-remove-form'));
|
||||
}
|
||||
|
||||
}
|
115
core/modules/book/src/Form/BookRemoveForm.php
Normal file
115
core/modules/book/src/Form/BookRemoveForm.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Form\BookRemoveForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Form;
|
||||
|
||||
use Drupal\book\BookManagerInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Remove form for book module.
|
||||
*/
|
||||
class BookRemoveForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The book manager.
|
||||
*
|
||||
* @var \Drupal\book\BookManagerInterface
|
||||
*/
|
||||
protected $bookManager;
|
||||
|
||||
/**
|
||||
* The node representing the book.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* Constructs a BookRemoveForm object.
|
||||
*
|
||||
* @param \Drupal\book\BookManagerInterface $book_manager
|
||||
* The book manager.
|
||||
*/
|
||||
public function __construct(BookManagerInterface $book_manager) {
|
||||
$this->bookManager = $book_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('book.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'book_remove_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $node = NULL) {
|
||||
$this->node = $node;
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
$title = array('%title' => $this->node->label());
|
||||
if ($this->node->book['has_children']) {
|
||||
return $this->t('%title has associated child pages, which will be relocated automatically to maintain their connection to the book. To recreate the hierarchy (as it was before removing this page), %title may be added again using the Outline tab, and each of its former child pages will need to be relocated manually.', $title);
|
||||
}
|
||||
else {
|
||||
return $this->t('%title may be added to hierarchy again using the Outline tab.', $title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Remove');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to remove %title from the book hierarchy?', array('%title' => $this->node->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->node->urlInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
if ($this->bookManager->checkNodeIsRemovable($this->node)) {
|
||||
$this->bookManager->deleteFromBook($this->node->id());
|
||||
drupal_set_message($this->t('The post has been removed from the book.'));
|
||||
}
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
89
core/modules/book/src/Form/BookSettingsForm.php
Normal file
89
core/modules/book/src/Form/BookSettingsForm.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Form\BookSettingsForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configure book settings for this site.
|
||||
*/
|
||||
class BookSettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'book_admin_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['book.settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$types = node_type_get_names();
|
||||
$config = $this->config('book.settings');
|
||||
$form['book_allowed_types'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Content types allowed in book outlines'),
|
||||
'#default_value' => $config->get('allowed_types'),
|
||||
'#options' => $types,
|
||||
'#description' => $this->t('Users with the %outline-perm permission can add all content types.', array('%outline-perm' => $this->t('Administer book outlines'))),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['book_child_type'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Content type for the <em>Add child page</em> link'),
|
||||
'#default_value' => $config->get('child_type'),
|
||||
'#options' => $types,
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['array_filter'] = array('#type' => 'value', '#value' => TRUE);
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$child_type = $form_state->getValue('book_child_type');
|
||||
if ($form_state->isValueEmpty(array('book_allowed_types', $child_type))) {
|
||||
$form_state->setErrorByName('book_child_type', $this->t('The content type for the %add-child link must be one of those selected as an allowed book outline type.', array('%add-child' => $this->t('Add child page'))));
|
||||
}
|
||||
|
||||
parent::validateForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$allowed_types = array_filter($form_state->getValue('book_allowed_types'));
|
||||
// We need to save the allowed types in an array ordered by machine_name so
|
||||
// that we can save them in the correct order if node type changes.
|
||||
// @see book_node_type_update().
|
||||
sort($allowed_types);
|
||||
$this->config('book.settings')
|
||||
// Remove unchecked types.
|
||||
->set('allowed_types', $allowed_types)
|
||||
->set('child_type', $form_state->getValue('book_child_type'))
|
||||
->save();
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
|
202
core/modules/book/src/Plugin/Block/BookNavigationBlock.php
Normal file
202
core/modules/book/src/Plugin/Block/BookNavigationBlock.php
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Plugin\Block\BookNavigationBlock.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\book\BookManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'Book navigation' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "book_navigation",
|
||||
* admin_label = @Translation("Book navigation"),
|
||||
* category = @Translation("Menus")
|
||||
* )
|
||||
*/
|
||||
class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The book manager.
|
||||
*
|
||||
* @var \Drupal\book\BookManagerInterface
|
||||
*/
|
||||
protected $bookManager;
|
||||
|
||||
/**
|
||||
* The node storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new BookNavigationBlock instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack object.
|
||||
* @param \Drupal\book\BookManagerInterface $book_manager
|
||||
* The book manager.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
|
||||
* The node storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RequestStack $request_stack, BookManagerInterface $book_manager, EntityStorageInterface $node_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->requestStack = $request_stack;
|
||||
$this->bookManager = $book_manager;
|
||||
$this->nodeStorage = $node_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('request_stack'),
|
||||
$container->get('book.manager'),
|
||||
$container->get('entity.manager')->getStorage('node')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
'block_mode' => "all pages",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function blockForm($form, FormStateInterface $form_state) {
|
||||
$options = array(
|
||||
'all pages' => $this->t('Show block on all pages'),
|
||||
'book pages' => $this->t('Show block only on book pages'),
|
||||
);
|
||||
$form['book_block_mode'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Book navigation block display'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->configuration['block_mode'],
|
||||
'#description' => $this->t("If <em>Show block on all pages</em> is selected, the block will contain the automatically generated menus for all of the site's books. If <em>Show block only on book pages</em> is selected, the block will contain only the one menu corresponding to the current page's book. In this case, if the current page is not in a book, no block will be displayed. The <em>Page specific visibility settings</em> or other visibility settings can be used in addition to selectively display this block."),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['block_mode'] = $form_state->getValue('book_block_mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$current_bid = 0;
|
||||
|
||||
if ($node = $this->requestStack->getCurrentRequest()->get('node')) {
|
||||
$current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
|
||||
}
|
||||
if ($this->configuration['block_mode'] == 'all pages') {
|
||||
$book_menus = array();
|
||||
$pseudo_tree = array(0 => array('below' => FALSE));
|
||||
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 = $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
|
||||
// is no reason to run an additional menu tree query for each book.
|
||||
$book['in_active_trail'] = FALSE;
|
||||
// Check whether user can access the book link.
|
||||
$book_node = $this->nodeStorage->load($book['nid']);
|
||||
$book['access'] = $book_node->access('view');
|
||||
$pseudo_tree[0]['link'] = $book;
|
||||
$book_menus[$book_id] = $this->bookManager->bookTreeOutput($pseudo_tree);
|
||||
}
|
||||
$book_menus[$book_id] += array(
|
||||
'#book_title' => $book['title'],
|
||||
);
|
||||
}
|
||||
if ($book_menus) {
|
||||
return array(
|
||||
'#theme' => 'book_all_books_block',
|
||||
) + $book_menus;
|
||||
}
|
||||
}
|
||||
elseif ($current_bid) {
|
||||
// Only display this block when the user is browsing a book.
|
||||
$query = \Drupal::entityQuery('node');
|
||||
$nid = $query->condition('nid', $node->book['bid'], '=')->execute();
|
||||
|
||||
// Only show the block if the user has view access for the top-level node.
|
||||
if ($nid) {
|
||||
$tree = $this->bookManager->bookTreeAllData($node->book['bid'], $node->book);
|
||||
// There should only be one element at the top level.
|
||||
$data = array_shift($tree);
|
||||
$below = $this->bookManager->bookTreeOutput($data['below']);
|
||||
if (!empty($below)) {
|
||||
return $below;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
// The "Book navigation" block must be cached per role and book navigation
|
||||
// context.
|
||||
return [
|
||||
'user.roles',
|
||||
'route.book_navigation',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @todo Make cacheable in https://www.drupal.org/node/2483181
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
730
core/modules/book/src/Tests/BookTest.php
Normal file
730
core/modules/book/src/Tests/BookTest.php
Normal file
|
@ -0,0 +1,730 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Tests\BookTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Create a book, add pages, and test book interface.
|
||||
*
|
||||
* @group book
|
||||
*/
|
||||
class BookTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('book', 'block', 'node_access_test');
|
||||
|
||||
/**
|
||||
* A book node.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $book;
|
||||
|
||||
/**
|
||||
* A user with permission to create and edit books.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $bookAuthor;
|
||||
|
||||
/**
|
||||
* A user with permission to view a book and access printer-friendly version.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* A user with permission to create and edit books and to administer blocks.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A user without the 'node test view' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUserWithoutNodeAccess;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
|
||||
// node_access_test requires a node_access_rebuild().
|
||||
node_access_rebuild();
|
||||
|
||||
// Create users.
|
||||
$this->bookAuthor = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books'));
|
||||
$this->webUser = $this->drupalCreateUser(array('access printer-friendly version', 'node test view'));
|
||||
$this->webUserWithoutNodeAccess = $this->drupalCreateUser(array('access printer-friendly version'));
|
||||
$this->adminUser = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'node test view', 'administer content types', 'administer site configuration'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new book with a page hierarchy.
|
||||
*/
|
||||
function createBook() {
|
||||
// Create new book.
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
|
||||
$this->book = $this->createBookNode('new');
|
||||
$book = $this->book;
|
||||
|
||||
/*
|
||||
* Add page hierarchy to book.
|
||||
* Book
|
||||
* |- Node 0
|
||||
* |- Node 1
|
||||
* |- Node 2
|
||||
* |- Node 3
|
||||
* |- Node 4
|
||||
*/
|
||||
$nodes = array();
|
||||
$nodes[] = $this->createBookNode($book->id()); // Node 0.
|
||||
$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.
|
||||
|
||||
$this->drupalLogout();
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests saving the book outline on an empty book.
|
||||
*/
|
||||
function testEmptyBook() {
|
||||
// Create a new empty book.
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
$book = $this->createBookNode('new');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Log in as a user with access to the book outline and save the form.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPostForm('admin/structure/book/' . $book->id(), array(), t('Save book pages'));
|
||||
$this->assertText(t('Updated book @book.', array('@book' => $book->label())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests book functionality through node interfaces.
|
||||
*/
|
||||
function testBook() {
|
||||
// Create new book.
|
||||
$nodes = $this->createBook();
|
||||
$book = $this->book;
|
||||
|
||||
$this->drupalLogin($this->webUser);
|
||||
|
||||
// Check that book pages display along with the correct outlines and
|
||||
// previous/next links.
|
||||
$this->checkBookNode($book, array($nodes[0], $nodes[3], $nodes[4]), FALSE, FALSE, $nodes[0], array());
|
||||
$this->checkBookNode($nodes[0], array($nodes[1], $nodes[2]), $book, $book, $nodes[1], array($book));
|
||||
$this->checkBookNode($nodes[1], NULL, $nodes[0], $nodes[0], $nodes[2], array($book, $nodes[0]));
|
||||
$this->checkBookNode($nodes[2], NULL, $nodes[1], $nodes[0], $nodes[3], array($book, $nodes[0]));
|
||||
$this->checkBookNode($nodes[3], NULL, $nodes[2], $book, $nodes[4], array($book));
|
||||
$this->checkBookNode($nodes[4], NULL, $nodes[3], $book, FALSE, array($book));
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
|
||||
// Check the presence of expected cache tags.
|
||||
$this->drupalGet('node/add/book');
|
||||
$this->assertCacheTag('config:book.settings');
|
||||
|
||||
/*
|
||||
* 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->webUser);
|
||||
// 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->bookAuthor);
|
||||
$other_book = $this->createBookNode('new');
|
||||
$node = $this->createBookNode($book->id());
|
||||
$edit = array('book[bid]' => $other_book->id());
|
||||
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($this->webUser);
|
||||
|
||||
// Check that the nodes in the second book are displayed correctly.
|
||||
// First we must set $this->book to the second book, so that the
|
||||
// correct regex will be generated for testing the outline.
|
||||
$this->book = $other_book;
|
||||
$this->checkBookNode($other_book, array($node), FALSE, FALSE, $node, array());
|
||||
$this->checkBookNode($node, NULL, $other_book, $other_book, FALSE, array($other_book));
|
||||
|
||||
// Test that we can save a book programatically.
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
$book = $this->createBookNode('new');
|
||||
$book->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the outline of sub-pages; previous, up, and next.
|
||||
*
|
||||
* Also checks the printer friendly version of the outline.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $node
|
||||
* Node to check.
|
||||
* @param $nodes
|
||||
* Nodes that should be in outline.
|
||||
* @param $previous
|
||||
* (optional) Previous link node. Defaults to FALSE.
|
||||
* @param $up
|
||||
* (optional) Up link node. Defaults to FALSE.
|
||||
* @param $next
|
||||
* (optional) Next link node. Defaults to FALSE.
|
||||
* @param array $breadcrumb
|
||||
* The nodes that should be displayed in the breadcrumb.
|
||||
*/
|
||||
function checkBookNode(EntityInterface $node, $nodes, $previous = FALSE, $up = FALSE, $next = FALSE, array $breadcrumb) {
|
||||
// $number does not use drupal_static as it should not be reset
|
||||
// since it uniquely identifies each call to checkBookNode().
|
||||
static $number = 0;
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
|
||||
// Check outline structure.
|
||||
if ($nodes !== NULL) {
|
||||
$this->assertPattern($this->generateOutlinePattern($nodes), format_string('Node @number outline confirmed.', array('@number' => $number)));
|
||||
}
|
||||
else {
|
||||
$this->pass(format_string('Node %number does not have outline.', array('%number' => $number)));
|
||||
}
|
||||
|
||||
// Check previous, up, and next links.
|
||||
if ($previous) {
|
||||
/** @var \Drupal\Core\Url $url */
|
||||
$url = $previous->urlInfo();
|
||||
$url->setOptions(array('attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page'))));
|
||||
$text = SafeMarkup::format('<b>‹</b> @label', array('@label' => $previous->label()));
|
||||
$this->assertRaw(\Drupal::l($text, $url), 'Previous page link found.');
|
||||
}
|
||||
|
||||
if ($up) {
|
||||
/** @var \Drupal\Core\Url $url */
|
||||
$url = $up->urlInfo();
|
||||
$url->setOptions(array('attributes' => array('title' => t('Go to parent page'))));
|
||||
$this->assertRaw(\Drupal::l('Up', $url), 'Up page link found.');
|
||||
}
|
||||
|
||||
if ($next) {
|
||||
/** @var \Drupal\Core\Url $url */
|
||||
$url = $next->urlInfo();
|
||||
$url->setOptions(array('attributes' => array('rel' => array('next'), 'title' => t('Go to next page'))));
|
||||
$text = SafeMarkup::format('@label <b>›</b>', array('@label' => $next->label()));
|
||||
$this->assertRaw(\Drupal::l($text, $url), 'Next page link found.');
|
||||
}
|
||||
|
||||
// Compute the expected breadcrumb.
|
||||
$expected_breadcrumb = array();
|
||||
$expected_breadcrumb[] = \Drupal::url('<front>');
|
||||
foreach ($breadcrumb as $a_node) {
|
||||
$expected_breadcrumb[] = $a_node->url();
|
||||
}
|
||||
|
||||
// Fetch links in the current breadcrumb.
|
||||
$links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
|
||||
$got_breadcrumb = array();
|
||||
foreach ($links as $link) {
|
||||
$got_breadcrumb[] = (string) $link['href'];
|
||||
}
|
||||
|
||||
// Compare expected and got breadcrumbs.
|
||||
$this->assertIdentical($expected_breadcrumb, $got_breadcrumb, 'The breadcrumb is correctly displayed on the page.');
|
||||
|
||||
// Check printer friendly version.
|
||||
$this->drupalGet('book/export/html/' . $node->id());
|
||||
$this->assertText($node->label(), 'Printer friendly title found.');
|
||||
$this->assertRaw($node->body->processed, 'Printer friendly body found.');
|
||||
|
||||
$number++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a regular expression to check for the sub-nodes in the outline.
|
||||
*
|
||||
* @param array $nodes
|
||||
* An array of nodes to check in outline.
|
||||
*
|
||||
* @return string
|
||||
* A regular expression that locates sub-nodes of the outline.
|
||||
*/
|
||||
function generateOutlinePattern($nodes) {
|
||||
$outline = '';
|
||||
foreach ($nodes as $node) {
|
||||
$outline .= '(node\/' . $node->id() . ')(.*?)(' . $node->label() . ')(.*?)';
|
||||
}
|
||||
|
||||
return '/<nav id="book-navigation-' . $this->book->id() . '"(.*?)<ul(.*?)' . $outline . '<\/ul>/s';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a book node.
|
||||
*
|
||||
* @param int|string $book_nid
|
||||
* A book node ID or set to 'new' to create a new book.
|
||||
* @param int|null $parent
|
||||
* (optional) Parent book reference ID. Defaults to NULL.
|
||||
*/
|
||||
function createBookNode($book_nid, $parent = NULL) {
|
||||
// $number does not use drupal_static as it should not be reset
|
||||
// since it uniquely identifies each call to createBookNode().
|
||||
static $number = 0; // Used to ensure that when sorted nodes stay in same order.
|
||||
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = $number . ' - SimpleTest test node ' . $this->randomMachineName(10);
|
||||
$edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomMachineName(32) . ' ' . $this->randomMachineName(32);
|
||||
$edit['book[bid]'] = $book_nid;
|
||||
|
||||
if ($parent !== NULL) {
|
||||
$this->drupalPostForm('node/add/book', $edit, t('Change book (update list of parents)'));
|
||||
|
||||
$edit['book[pid]'] = $parent;
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
// Make sure the parent was flagged as having children.
|
||||
$parent_node = \Drupal::entityManager()->getStorage('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'));
|
||||
}
|
||||
|
||||
// Check to make sure the book node was created.
|
||||
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.');
|
||||
$number++;
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests book export ("printer-friendly version") functionality.
|
||||
*/
|
||||
function testBookExport() {
|
||||
// Create a book.
|
||||
$nodes = $this->createBook();
|
||||
|
||||
// Login as web user and view printer-friendly version.
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->drupalGet('node/' . $this->book->id());
|
||||
$this->clickLink(t('Printer-friendly version'));
|
||||
|
||||
// Make sure each part of the book is there.
|
||||
foreach ($nodes as $node) {
|
||||
$this->assertText($node->label(), 'Node title found in printer friendly version.');
|
||||
$this->assertRaw($node->body->processed, 'Node body found in printer friendly version.');
|
||||
}
|
||||
|
||||
// Make sure we can't export an unsupported format.
|
||||
$this->drupalGet('book/export/foobar/' . $this->book->id());
|
||||
$this->assertResponse('404', 'Unsupported export format returned "not found".');
|
||||
|
||||
// Make sure we get a 404 on a not existing book node.
|
||||
$this->drupalGet('book/export/html/123');
|
||||
$this->assertResponse('404', 'Not existing book node returned "not found".');
|
||||
|
||||
// Make sure an anonymous user cannot view printer-friendly version.
|
||||
$this->drupalLogout();
|
||||
|
||||
// Load the book and verify there is no printer-friendly version link.
|
||||
$this->drupalGet('node/' . $this->book->id());
|
||||
$this->assertNoLink(t('Printer-friendly version'), 'Anonymous user is not shown link to printer-friendly version.');
|
||||
|
||||
// Try getting the URL directly, and verify it fails.
|
||||
$this->drupalGet('book/export/html/' . $this->book->id());
|
||||
$this->assertResponse('403', 'Anonymous user properly forbidden.');
|
||||
|
||||
// Now grant anonymous users permission to view the printer-friendly
|
||||
// version and verify that node access restrictions still prevent them from
|
||||
// seeing it.
|
||||
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('access printer-friendly version'));
|
||||
$this->drupalGet('book/export/html/' . $this->book->id());
|
||||
$this->assertResponse('403', 'Anonymous user properly forbidden from seeing the printer-friendly version when denied by node access.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the functionality of the book navigation block.
|
||||
*/
|
||||
function testBookNavigationBlock() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Enable the block.
|
||||
$block = $this->drupalPlaceBlock('book_navigation');
|
||||
|
||||
// Give anonymous users the permission 'node test view'.
|
||||
$edit = array();
|
||||
$edit[RoleInterface::ANONYMOUS_ID . '[node test view]'] = TRUE;
|
||||
$this->drupalPostForm('admin/people/permissions/' . RoleInterface::ANONYMOUS_ID, $edit, t('Save permissions'));
|
||||
$this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users.");
|
||||
|
||||
// Test correct display of the block.
|
||||
$nodes = $this->createBook();
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertText($block->label(), 'Book navigation block is displayed.');
|
||||
$this->assertText($this->book->label(), format_string('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->label())));
|
||||
$this->assertNoText($nodes[0]->label(), 'No links to individual book pages are displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the book navigation block when an access module is installed.
|
||||
*/
|
||||
function testNavigationBlockOnAccessModuleInstalled() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$block = $this->drupalPlaceBlock('book_navigation', array('block_mode' => 'book pages'));
|
||||
|
||||
// Give anonymous users the permission 'node test view'.
|
||||
$edit = array();
|
||||
$edit[RoleInterface::ANONYMOUS_ID . '[node test view]'] = TRUE;
|
||||
$this->drupalPostForm('admin/people/permissions/' . RoleInterface::ANONYMOUS_ID, $edit, t('Save permissions'));
|
||||
$this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users.");
|
||||
|
||||
// Create a book.
|
||||
$this->createBook();
|
||||
|
||||
// Test correct display of the block to registered users.
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->drupalGet('node/' . $this->book->id());
|
||||
$this->assertText($block->label(), 'Book navigation block is displayed to registered users.');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Test correct display of the block to anonymous users.
|
||||
$this->drupalGet('node/' . $this->book->id());
|
||||
$this->assertText($block->label(), 'Book navigation block is displayed to anonymous users.');
|
||||
|
||||
// Test the 'book pages' block_mode setting.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertNoText($block->label(), 'Book navigation block is not shown on non-book pages.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the access for deleting top-level book nodes.
|
||||
*/
|
||||
function testBookDelete() {
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
$nodes = $this->createBook();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$edit = array();
|
||||
|
||||
// Test access to delete top-level and child book nodes.
|
||||
$this->drupalGet('node/' . $this->book->id() . '/outline/remove');
|
||||
$this->assertResponse('403', 'Deleting top-level book node properly forbidden.');
|
||||
$this->drupalPostForm('node/' . $nodes[4]->id() . '/outline/remove', $edit, t('Remove'));
|
||||
$node_storage->resetCache(array($nodes[4]->id()));
|
||||
$node4 = $node_storage->load($nodes[4]->id());
|
||||
$this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.');
|
||||
|
||||
// Delete all child book nodes and retest top-level node deletion.
|
||||
foreach ($nodes as $node) {
|
||||
$nids[] = $node->id();
|
||||
}
|
||||
entity_delete_multiple('node', $nids);
|
||||
$this->drupalPostForm('node/' . $this->book->id() . '/outline/remove', $edit, t('Remove'));
|
||||
$node_storage->resetCache(array($this->book->id()));
|
||||
$node = $node_storage->load($this->book->id());
|
||||
$this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.');
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests node type changing machine name when type is a book allowed type.
|
||||
*/
|
||||
function testBookNodeTypeChange() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// Change the name, machine name and description.
|
||||
$edit = array(
|
||||
'name' => 'Bar',
|
||||
'type' => 'bar',
|
||||
);
|
||||
$this->drupalPostForm('admin/structure/types/manage/book', $edit, t('Save content type'));
|
||||
|
||||
// Ensure that the config book.settings:allowed_types has been updated with
|
||||
// the new machine and the old one has been removed.
|
||||
$this->assertTrue(book_type_is_allowed('bar'), 'Config book.settings:allowed_types contains the updated node type machine name "bar".');
|
||||
$this->assertFalse(book_type_is_allowed('book'), 'Config book.settings:allowed_types does not contain the old node type machine name "book".');
|
||||
|
||||
$edit = array(
|
||||
'name' => 'Basic page',
|
||||
'title_label' => 'Title for basic page',
|
||||
'type' => 'page',
|
||||
);
|
||||
$this->drupalPostForm('admin/structure/types/add', $edit, t('Save content type'));
|
||||
|
||||
// Add page to the allowed node types.
|
||||
$edit = array(
|
||||
'book_allowed_types[page]' => 'page',
|
||||
'book_allowed_types[bar]' => 'bar',
|
||||
);
|
||||
|
||||
$this->drupalPostForm('admin/structure/book/settings', $edit, t('Save configuration'));
|
||||
$this->assertTrue(book_type_is_allowed('bar'), 'Config book.settings:allowed_types contains the bar node type.');
|
||||
$this->assertTrue(book_type_is_allowed('page'), 'Config book.settings:allowed_types contains the page node type.');
|
||||
|
||||
// Test the order of the book.settings::allowed_types configuration is as
|
||||
// expected. The point of this test is to prove that after changing a node
|
||||
// type going to admin/structure/book/settings and pressing save without
|
||||
// changing anything should not alter the book.settings configuration. The
|
||||
// order will be:
|
||||
// @code
|
||||
// array(
|
||||
// 'bar',
|
||||
// 'page',
|
||||
// );
|
||||
// @endcode
|
||||
$current_config = $this->config('book.settings')->get();
|
||||
$this->drupalPostForm('admin/structure/book/settings', array(), t('Save configuration'));
|
||||
$this->assertIdentical($current_config, $this->config('book.settings')->get());
|
||||
|
||||
// Change the name, machine name and description.
|
||||
$edit = array(
|
||||
'name' => 'Zebra book',
|
||||
'type' => 'zebra',
|
||||
);
|
||||
$this->drupalPostForm('admin/structure/types/manage/bar', $edit, t('Save content type'));
|
||||
$this->assertTrue(book_type_is_allowed('zebra'), 'Config book.settings:allowed_types contains the zebra node type.');
|
||||
$this->assertTrue(book_type_is_allowed('page'), 'Config book.settings:allowed_types contains the page node type.');
|
||||
|
||||
// Test the order of the book.settings::allowed_types configuration is as
|
||||
// expected. The order should be:
|
||||
// @code
|
||||
// array(
|
||||
// 'page',
|
||||
// 'zebra',
|
||||
// );
|
||||
// @endcode
|
||||
$current_config = $this->config('book.settings')->get();
|
||||
$this->drupalPostForm('admin/structure/book/settings', array(), t('Save configuration'));
|
||||
$this->assertIdentical($current_config, $this->config('book.settings')->get());
|
||||
|
||||
$edit = array(
|
||||
'name' => 'Animal book',
|
||||
'type' => 'zebra',
|
||||
);
|
||||
$this->drupalPostForm('admin/structure/types/manage/zebra', $edit, t('Save content type'));
|
||||
|
||||
// Test the order of the book.settings::allowed_types configuration is as
|
||||
// expected. The order should be:
|
||||
// @code
|
||||
// array(
|
||||
// 'page',
|
||||
// 'zebra',
|
||||
// );
|
||||
// @endcode
|
||||
$current_config = $this->config('book.settings')->get();
|
||||
$this->drupalPostForm('admin/structure/book/settings', array(), t('Save configuration'));
|
||||
$this->assertIdentical($current_config, $this->config('book.settings')->get());
|
||||
|
||||
// Ensure that after all the node type changes book.settings:child_type has
|
||||
// the expected value.
|
||||
$this->assertEqual($this->config('book.settings')->get('child_type'), 'zebra');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests re-ordering of books.
|
||||
*/
|
||||
public function testBookOrdering() {
|
||||
// Create new book.
|
||||
$this->createBook();
|
||||
$book = $this->book;
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$node1 = $this->createBookNode($book->id());
|
||||
$node2 = $this->createBookNode($book->id());
|
||||
$pid = $node1->book['nid'];
|
||||
|
||||
// Head to admin screen and attempt to re-order.
|
||||
$this->drupalGet('admin/structure/book/' . $book->id());
|
||||
$edit = array(
|
||||
"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()}][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()}][pid]", $pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests outline of a book.
|
||||
*/
|
||||
public function testBookOutline() {
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
|
||||
// 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->adminUser);
|
||||
$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()->getStorage('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->bookAuthor);
|
||||
$book = $this->createBookNode('new');
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$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()->getStorage('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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that saveBookLink() returns something.
|
||||
*/
|
||||
public function testSaveBookLink() {
|
||||
$book_manager = \Drupal::service('book.manager');
|
||||
|
||||
// Mock a link for a new book.
|
||||
$link = array('nid' => 1, 'has_children' => 0, 'original_bid' => 0, 'parent_depth_limit' => 8, 'pid' => 0, 'weight' => 0, 'bid' => 1);
|
||||
$new = TRUE;
|
||||
|
||||
// Save the link.
|
||||
$return = $book_manager->saveBookLink($link, $new);
|
||||
|
||||
// Add the link defaults to $link so we have something to compare to the return from saveBookLink().
|
||||
$link += $book_manager->getLinkDefaults($link['nid']);
|
||||
|
||||
// Test the return from saveBookLink.
|
||||
$this->assertEqual($return, $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the listing of all books.
|
||||
*/
|
||||
public function testBookListing() {
|
||||
// Create a new book.
|
||||
$this->createBook();
|
||||
|
||||
// Must be a user with 'node test view' permission since node_access_test is installed.
|
||||
$this->drupalLogin($this->webUser);
|
||||
|
||||
// Load the book page and assert the created book title is displayed.
|
||||
$this->drupalGet('book');
|
||||
|
||||
$this->assertText($this->book->label(), 'The book title is displayed on the book listing page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the administrative listing of all books.
|
||||
*/
|
||||
public function testAdminBookListing() {
|
||||
// Create a new book.
|
||||
$this->createBook();
|
||||
|
||||
// Load the book page and assert the created book title is displayed.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/structure/book');
|
||||
$this->assertText($this->book->label(), 'The book title is displayed on the administrative book listing page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the administrative listing of all book pages in a book.
|
||||
*/
|
||||
public function testAdminBookNodeListing() {
|
||||
// Create a new book.
|
||||
$this->createBook();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Load the book page list and assert the created book title is displayed
|
||||
// and action links are shown on list items.
|
||||
$this->drupalGet('admin/structure/book/' . $this->book->id());
|
||||
$this->assertText($this->book->label(), 'The book title is displayed on the administrative book listing page.');
|
||||
|
||||
$elements = $this->xpath('//table//ul[@class="dropbutton"]/li/a');
|
||||
$this->assertEqual((string) $elements[0], 'View', 'View link is found from the list.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the loaded book in hook_node_load() does not depend on the user.
|
||||
*/
|
||||
public function testHookNodeLoadAccess() {
|
||||
\Drupal::service('module_installer')->install(['node_access_test']);
|
||||
|
||||
// Ensure that the loaded book in hook_node_load() does NOT depend on the
|
||||
// current user.
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
$this->book = $this->createBookNode('new');
|
||||
// Reset any internal static caching.
|
||||
$node_storage = \Drupal::entityManager()->getStorage('node');
|
||||
$node_storage->resetCache();
|
||||
|
||||
// Login as user without access to the book node, so no 'node test view'
|
||||
// permission.
|
||||
// @see node_access_test_node_grants().
|
||||
$this->drupalLogin($this->webUserWithoutNodeAccess);
|
||||
$book_node = $node_storage->load($this->book->id());
|
||||
$this->assertTrue(!empty($book_node->book));
|
||||
$this->assertEqual($book_node->book['bid'], $this->book->id());
|
||||
|
||||
// Reset the internal cache to retrigger the hook_node_load() call.
|
||||
$node_storage->resetCache();
|
||||
|
||||
$this->drupalLogin($this->webUser);
|
||||
$book_node = $node_storage->load($this->book->id());
|
||||
$this->assertTrue(!empty($book_node->book));
|
||||
$this->assertEqual($book_node->book['bid'], $this->book->id());
|
||||
}
|
||||
|
||||
}
|
99
core/modules/book/src/Tests/BookUninstallTest.php
Normal file
99
core/modules/book/src/Tests/BookUninstallTest.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Tests\BookUninstallTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Tests;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the Book module cannot be uninstalled if books exist.
|
||||
*
|
||||
* @group book
|
||||
*/
|
||||
class BookUninstallTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'user', 'field', 'filter', 'text', 'entity_reference', 'node', 'book');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installSchema('book', array('book'));
|
||||
$this->installSchema('node', array('node_access'));
|
||||
$this->installConfig(array('node', 'book', 'field'));
|
||||
// For uninstall to work.
|
||||
$this->installSchema('user', array('users_data'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the book_system_info_alter() method.
|
||||
*/
|
||||
public function testBookUninstall() {
|
||||
// No nodes exist.
|
||||
$validation_reasons = \Drupal::service('module_installer')->validateUninstall(['book']);
|
||||
$this->assertEqual([], $validation_reasons, 'The book module is not required.');
|
||||
|
||||
$content_type = NodeType::create(array(
|
||||
'type' => $this->randomMachineName(),
|
||||
'name' => $this->randomString(),
|
||||
));
|
||||
$content_type->save();
|
||||
$book_config = $this->config('book.settings');
|
||||
$allowed_types = $book_config->get('allowed_types');
|
||||
$allowed_types[] = $content_type->id();
|
||||
$book_config->set('allowed_types', $allowed_types)->save();
|
||||
|
||||
$node = Node::create(array('type' => $content_type->id()));
|
||||
$node->book['bid'] = 'new';
|
||||
$node->save();
|
||||
|
||||
// One node in a book but not of type book.
|
||||
$validation_reasons = \Drupal::service('module_installer')->validateUninstall(['book']);
|
||||
$this->assertEqual(['To uninstall Book, delete all content that is part of a book'], $validation_reasons['book']);
|
||||
|
||||
$book_node = Node::create(array('type' => 'book'));
|
||||
$book_node->book['bid'] = FALSE;
|
||||
$book_node->save();
|
||||
|
||||
// Two nodes, one in a book but not of type book and one book node (which is
|
||||
// not in a book).
|
||||
$validation_reasons = \Drupal::service('module_installer')->validateUninstall(['book']);
|
||||
$this->assertEqual(['To uninstall Book, delete all content that is part of a book'], $validation_reasons['book']);
|
||||
|
||||
$node->delete();
|
||||
// One node of type book but not actually part of a book.
|
||||
$validation_reasons = \Drupal::service('module_installer')->validateUninstall(['book']);
|
||||
$this->assertEqual(['To uninstall Book, delete all content that has the Book content type'], $validation_reasons['book']);
|
||||
|
||||
$book_node->delete();
|
||||
// No nodes exist therefore the book module is not required.
|
||||
$module_data = _system_rebuild_module_data();
|
||||
$this->assertFalse(isset($module_data['book']->info['required']), 'The book module is not required.');
|
||||
|
||||
$node = Node::create(array('type' => $content_type->id()));
|
||||
$node->save();
|
||||
// One node exists but is not part of a book therefore the book module is
|
||||
// not required.
|
||||
$validation_reasons = \Drupal::service('module_installer')->validateUninstall(['book']);
|
||||
$this->assertEqual([], $validation_reasons, 'The book module is not required.');
|
||||
|
||||
// Uninstall the Book module and check the node type is deleted.
|
||||
\Drupal::service('module_installer')->uninstall(array('book'));
|
||||
$this->assertNull(NodeType::load('book'), "The book node type does not exist.");
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue