First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,23 @@
<?php
/**
* @file
* Default theme implementation for rendering book outlines within a block.
*
* This template is used only when the block is configured to "show block on all
* pages", which presents multiple independent books on all pages.
*
* Available variables:
* - $book_menus: Array of book outlines keyed to the parent book ID. Call
* render() on each to print it as an unordered list.
*
* @see template_preprocess_book_all_books_block()
*
* @ingroup themeable
*/
?>
<?php foreach ($book_menus as $book_id => $menu): ?>
<div id="book-block-menu-<?php print $book_id; ?>" class="book-block-menu">
<?php print render($menu); ?>
</div>
<?php endforeach; ?>

View file

@ -0,0 +1,52 @@
<?php
/**
* @file
* Default theme implementation for printed version of book outline.
*
* Available variables:
* - $title: Top level node title.
* - $head: Header tags.
* - $language: Language code. e.g. "en" for english.
* - $language_rtl: TRUE or FALSE depending on right to left language scripts.
* - $base_url: URL to home page.
* - $contents: Nodes within the current outline rendered through
* book-node-export-html.tpl.php.
*
* @see template_preprocess_book_export_html()
*
* @ingroup themeable
*/
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language->language; ?>" xml:lang="<?php print $language->language; ?>" dir="<?php print $dir; ?>">
<head>
<title><?php print $title; ?></title>
<?php print $head; ?>
<base href="<?php print $base_url; ?>" />
<link type="text/css" rel="stylesheet" href="misc/print.css" />
<?php if ($language_rtl): ?>
<link type="text/css" rel="stylesheet" href="misc/print-rtl.css" />
<?php endif; ?>
</head>
<body>
<?php
/**
* 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) &lt;div&gt; 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.
*/
$div_close = '';
?>
<?php for ($i = 1; $i < $depth; $i++): ?>
<div class="section-<?php print $i; ?>">
<?php $div_close .= '</div>'; ?>
<?php endfor; ?>
<?php print $contents; ?>
<?php print $div_close; ?>
</body>
</html>

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* Default theme implementation to navigate books.
*
* Presented under nodes that are a part of book outlines.
*
* Available variables:
* - $tree: The immediate children of the current node rendered as an unordered
* list.
* - $current_depth: Depth of the current node within the book outline. Provided
* for context.
* - $prev_url: URL to the previous node.
* - $prev_title: Title of the previous node.
* - $parent_url: URL to the parent node.
* - $parent_title: Title of the parent node. Not printed by default. Provided
* as an option.
* - $next_url: URL to the next node.
* - $next_title: Title of the next node.
* - $has_links: Flags TRUE whenever the previous, parent or next data has a
* value.
* - $book_id: The book ID of the current outline being viewed. Same as the node
* ID containing the entire outline. Provided for context.
* - $book_url: The book/node URL of the current outline being viewed. Provided
* as an option. Not used by default.
* - $book_title: The book/node title of the current outline being viewed.
* Provided as an option. Not used by default.
*
* @see template_preprocess_book_navigation()
*
* @ingroup themeable
*/
?>
<?php if ($tree || $has_links): ?>
<div id="book-navigation-<?php print $book_id; ?>" class="book-navigation">
<?php print $tree; ?>
<?php if ($has_links): ?>
<div class="page-links clearfix">
<?php if ($prev_url): ?>
<a href="<?php print $prev_url; ?>" class="page-previous" title="<?php print t('Go to previous page'); ?>"><?php print t(' ') . $prev_title; ?></a>
<?php endif; ?>
<?php if ($parent_url): ?>
<a href="<?php print $parent_url; ?>" class="page-up" title="<?php print t('Go to parent page'); ?>"><?php print t('up'); ?></a>
<?php endif; ?>
<?php if ($next_url): ?>
<a href="<?php print $next_url; ?>" class="page-next" title="<?php print t('Go to next page'); ?>"><?php print $next_title . t(' '); ?></a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>

View file

@ -0,0 +1,25 @@
<?php
/**
* @file
* Default theme implementation for a single node in a printer-friendly outline.
*
* @see book-node-export-html.tpl.php
* Where it is collected and printed out.
*
* Available variables:
* - $depth: Depth of the current node inside the outline.
* - $title: Node title.
* - $content: Node content.
* - $children: All the child nodes recursively rendered through this file.
*
* @see template_preprocess_book_node_export_html()
*
* @ingroup themeable
*/
?>
<div id="node-<?php print $node->nid; ?>" class="section-<?php print $depth; ?>">
<h1 class="book-heading"><?php print $title; ?></h1>
<?php print $content; ?>
<?php print $children; ?>
</div>

15
modules/book/book-rtl.css Normal file
View file

@ -0,0 +1,15 @@
/**
* @file
* Right-to-Left styling for the Book module.
*/
.book-navigation .menu {
padding: 1em 3em 0 0;
}
.book-navigation .page-previous {
float: right;
}
.book-navigation .page-up {
float: right;
}

289
modules/book/book.admin.inc Normal file
View file

@ -0,0 +1,289 @@
<?php
/**
* @file
* Administration page callbacks for the Book module.
*/
/**
* Returns an administrative overview of all books.
*
* @return string
* A HTML-formatted string with the administrative page content.
*
* @see book_menu()
*/
function book_admin_overview() {
$rows = array();
$headers = array(t('Book'), t('Operations'));
// Add any recognized books to the table list.
foreach (book_get_books() as $book) {
$rows[] = array(l($book['title'], $book['href'], $book['options']), l(t('edit order and titles'), 'admin/content/book/' . $book['nid']));
}
return theme('table', array('header' => $headers, 'rows' => $rows, 'empty' => t('No books available.')));
}
/**
* Form constructor for the book settings form.
*
* @see book_admin_settings_validate()
*
* @ingroup forms
*/
function book_admin_settings() {
$types = node_type_get_names();
$form['book_allowed_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Content types allowed in book outlines'),
'#default_value' => variable_get('book_allowed_types', array('book')),
'#options' => $types,
'#description' => t('Users with the %outline-perm permission can add all content types.', array('%outline-perm' => t('Administer book outlines'))),
'#required' => TRUE,
);
$form['book_child_type'] = array(
'#type' => 'radios',
'#title' => t('Content type for child pages'),
'#default_value' => variable_get('book_child_type', 'book'),
'#options' => $types,
'#required' => TRUE,
);
$form['array_filter'] = array('#type' => 'value', '#value' => TRUE);
$form['#validate'][] = 'book_admin_settings_validate';
return system_settings_form($form);
}
/**
* Form validation handler for book_admin_settings().
*
* @see book_admin_settings_submit()
*/
function book_admin_settings_validate($form, &$form_state) {
$child_type = $form_state['values']['book_child_type'];
if (empty($form_state['values']['book_allowed_types'][$child_type])) {
form_set_error('book_child_type', t('The content type for the %add-child link must be one of those selected as an allowed book outline type.', array('%add-child' => t('Add child page'))));
}
}
/**
* Form constructor for administering a single book's hierarchy.
*
* @see book_admin_edit_submit()
*
* @param $node
* The node of the top-level page in the book.
*
* @see book_admin_edit_validate()
* @see book_admin_edit_submit()
* @ingroup forms
*/
function book_admin_edit($form, $form_state, $node) {
drupal_set_title($node->title);
$form['#node'] = $node;
_book_admin_table($node, $form);
$form['save'] = array(
'#type' => 'submit',
'#value' => t('Save book pages'),
);
return $form;
}
/**
* Form validation handler for book_admin_edit().
*
* Checks that the book has not been changed while using the form.
*
* @see book_admin_edit_submit()
*/
function book_admin_edit_validate($form, &$form_state) {
if ($form_state['values']['tree_hash'] != $form_state['values']['tree_current_hash']) {
form_set_error('', t('This book has been modified by another user, the changes could not be saved.'));
}
}
/**
* Form submission handler for book_admin_edit().
*
* This function takes care to save parent menu items before their children.
* Saving menu items in the incorrect order can break the menu tree.
*
* @see book_admin_edit_validate()
* @see menu_overview_form_submit()
*/
function book_admin_edit_submit($form, &$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.
$order = array_flip(array_keys($form_state['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['values']['table'][$key];
// Update menu item if moved.
if ($row['plid']['#default_value'] != $values['plid'] || $row['weight']['#default_value'] != $values['weight']) {
$row['#item']['plid'] = $values['plid'];
$row['#item']['weight'] = $values['weight'];
menu_link_save($row['#item']);
}
// Update the title if changed.
if ($row['title']['#default_value'] != $values['title']) {
$node = node_load($values['nid']);
$langcode = LANGUAGE_NONE;
$node->title = $values['title'];
$node->book['link_title'] = $values['title'];
$node->revision = 1;
$node->log = t('Title changed from %original to %current.', array('%original' => $node->title, '%current' => $values['title']));
node_save($node);
watchdog('content', 'book: updated %title.', array('%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid));
}
}
}
drupal_set_message(t('Updated book %title.', array('%title' => $form['#node']->title)));
}
/**
* Builds the table portion of the form for the book administration page.
*
* @param $node
* The node of the top-level page in the book.
* @param $form
* The form that is being modified, passed by reference.
*
* @see book_admin_edit()
*/
function _book_admin_table($node, &$form) {
$form['table'] = array(
'#theme' => 'book_admin_table',
'#tree' => TRUE,
);
$tree = book_menu_subtree_data($node->book);
$tree = array_shift($tree); // Do not include the book item itself.
if ($tree['below']) {
$hash = drupal_hash_base64(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,
);
_book_admin_table_tree($tree['below'], $form['table']);
}
}
/**
* Helps build the main table in the book administration page form.
*
* @param $tree
* A subtree of the book menu hierarchy.
* @param $form
* The form that is being modified, passed by reference.
*
* @return
* The modified form array.
*
* @see book_admin_edit()
*/
function _book_admin_table_tree($tree, &$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;
foreach ($tree as $data) {
$form['book-admin-' . $data['link']['nid']] = array(
'#item' => $data['link'],
'nid' => array('#type' => 'value', '#value' => $data['link']['nid']),
'depth' => array('#type' => 'value', '#value' => $data['link']['depth']),
'href' => array('#type' => 'value', '#value' => $data['link']['href']),
'title' => array(
'#type' => 'textfield',
'#default_value' => $data['link']['link_title'],
'#maxlength' => 255,
'#size' => 40,
),
'weight' => array(
'#type' => 'weight',
'#default_value' => $data['link']['weight'],
'#delta' => max($delta, abs($data['link']['weight'])),
'#title' => t('Weight for @title', array('@title' => $data['link']['title'])),
'#title_display' => 'invisible',
),
'plid' => array(
'#type' => 'hidden',
'#default_value' => $data['link']['plid'],
),
'mlid' => array(
'#type' => 'hidden',
'#default_value' => $data['link']['mlid'],
),
);
if ($data['below']) {
_book_admin_table_tree($data['below'], $form);
}
}
return $form;
}
/**
* Returns HTML for a book administration form.
*
* @param $variables
* An associative array containing:
* - form: A render element representing the form.
*
* @see book_admin_table()
* @ingroup themeable
*/
function theme_book_admin_table($variables) {
$form = $variables['form'];
drupal_add_tabledrag('book-outline', 'match', 'parent', 'book-plid', 'book-plid', 'book-mlid', TRUE, MENU_MAX_DEPTH - 2);
drupal_add_tabledrag('book-outline', 'order', 'sibling', 'book-weight');
$header = array(t('Title'), t('Weight'), t('Parent'), array('data' => t('Operations'), 'colspan' => '3'));
$rows = array();
$destination = drupal_get_destination();
$access = user_access('administer nodes');
foreach (element_children($form) as $key) {
$nid = $form[$key]['nid']['#value'];
$href = $form[$key]['href']['#value'];
// Add special classes to be used with tabledrag.js.
$form[$key]['plid']['#attributes']['class'] = array('book-plid');
$form[$key]['mlid']['#attributes']['class'] = array('book-mlid');
$form[$key]['weight']['#attributes']['class'] = array('book-weight');
$data = array(
theme('indentation', array('size' => $form[$key]['depth']['#value'] - 2)) . drupal_render($form[$key]['title']),
drupal_render($form[$key]['weight']),
drupal_render($form[$key]['plid']) . drupal_render($form[$key]['mlid']),
l(t('view'), $href),
$access ? l(t('edit'), 'node/' . $nid . '/edit', array('query' => $destination)) : '&nbsp;',
$access ? l(t('delete'), 'node/' . $nid . '/delete', array('query' => $destination) ) : '&nbsp;',
);
$row = array('data' => $data);
if (isset($form[$key]['#attributes'])) {
$row = array_merge($row, $form[$key]['#attributes']);
}
$row['class'][] = 'draggable';
$rows[] = $row;
}
return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'book-outline'), 'empty' => t('No book content available.')));
}

58
modules/book/book.css Normal file
View file

@ -0,0 +1,58 @@
/**
* @file
* Styling for the Book module.
*/
.book-navigation .menu {
border-top: 1px solid #888;
padding: 1em 0 0 3em; /* LTR */
}
.book-navigation .page-links {
border-top: 1px solid #888;
border-bottom: 1px solid #888;
text-align: center;
padding: 0.5em;
}
.book-navigation .page-previous {
text-align: left;
width: 42%;
display: block;
float: left; /* LTR */
}
.book-navigation .page-up {
margin: 0 5%;
width: 4%;
display: block;
float: left; /* LTR */
}
.book-navigation .page-next {
text-align: right;
width: 42%;
display: block;
float: right;
}
#book-outline {
min-width: 56em;
}
.book-outline-form .form-item {
margin-top: 0;
margin-bottom: 0;
}
html.js #edit-book-pick-book {
display: none;
}
.form-item-book-bid .description {
clear: both;
}
#book-admin-edit select {
margin-right: 24px;
}
#book-admin-edit select.progress-disabled {
margin-right: 0;
}
#book-admin-edit tr.ajax-new-content {
background-color: #ffd;
}
#book-admin-edit .form-item {
float: left;
}

14
modules/book/book.info Normal file
View file

@ -0,0 +1,14 @@
name = Book
description = Allows users to create and organize related content in an outline.
package = Core
version = VERSION
core = 7.x
files[] = book.test
configure = admin/content/book/settings
stylesheets[all][] = book.css
; Information added by Drupal.org packaging script on 2017-06-21
version = "7.56"
project = "drupal"
datestamp = "1498069849"

95
modules/book/book.install Normal file
View file

@ -0,0 +1,95 @@
<?php
/**
* @file
* Install, update and uninstall functions for the book module.
*/
/**
* Implements hook_install().
*/
function book_install() {
// Add the node type.
_book_install_type_create();
}
/**
* Implements hook_uninstall().
*/
function book_uninstall() {
variable_del('book_allowed_types');
variable_del('book_child_type');
variable_del('book_block_mode');
// Delete menu links.
db_delete('menu_links')
->condition('module', 'book')
->execute();
menu_cache_clear_all();
}
/**
* Creates the book content type.
*/
function _book_install_type_create() {
// Create an additional node type.
$book_node_type = array(
'type' => 'book',
'name' => t('Book page'),
'base' => 'node_content',
'description' => t('<em>Books</em> have a built-in hierarchical navigation. Use for handbooks or tutorials.'),
'custom' => 1,
'modified' => 1,
'locked' => 0,
);
$book_node_type = node_type_set_defaults($book_node_type);
node_type_save($book_node_type);
node_add_body_field($book_node_type);
// Default to not promoted.
variable_set('node_options_book', array('status'));
// Use this default type for adding content to books.
variable_set('book_allowed_types', array('book'));
variable_set('book_child_type', 'book');
}
/**
* Implements hook_schema().
*/
function book_schema() {
$schema['book'] = array(
'description' => 'Stores book outline information. Uniquely connects each node in the outline to a link in {menu_links}',
'fields' => array(
'mlid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => "The book page's {menu_links}.mlid.",
),
'nid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => "The book page's {node}.nid.",
),
'bid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => "The book ID is the {book}.nid of the top-level page.",
),
),
'primary key' => array('mlid'),
'unique keys' => array(
'nid' => array('nid'),
),
'indexes' => array(
'bid' => array('bid'),
),
);
return $schema;
}

27
modules/book/book.js Normal file
View file

@ -0,0 +1,27 @@
/**
* @file
* Javascript behaviors for the Book module.
*/
(function ($) {
Drupal.behaviors.bookFieldsetSummaries = {
attach: function (context) {
$('fieldset.book-outline-form', context).drupalSetSummary(function (context) {
var $select = $('.form-item-book-bid select');
var val = $select.val();
if (val === '0') {
return Drupal.t('Not in book');
}
else if (val === 'new') {
return Drupal.t('New book');
}
else {
return Drupal.checkPlain($select.find(':selected').text());
}
});
}
};
})(jQuery);

1437
modules/book/book.module Normal file

File diff suppressed because it is too large Load diff

247
modules/book/book.pages.inc Normal file
View file

@ -0,0 +1,247 @@
<?php
/**
* @file
* User page callbacks for the book module.
*/
/**
* Menu callback: Prints a listing of all books.
*
* @return string
* A HTML-formatted string with the listing of all books content.
*
* @see book_menu()
*/
function book_render() {
$book_list = array();
foreach (book_get_books() as $book) {
$book_list[] = l($book['title'], $book['href'], $book['options']);
}
return theme('item_list', array('items' => $book_list));
}
/**
* Menu callback; Generates representations of a book page and its children.
*
* The function delegates the generation of output to helper functions. The
* function name is derived by prepending 'book_export_' to the given output
* type. So, e.g., a type of 'html' results in a call to the function
* book_export_html().
*
* @param $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 $nid
* An integer representing the node id (nid) of the node to export
*
* @return
* A string representing the node and its children in the book hierarchy in a
* format determined by the $type parameter.
*
* @see book_menu()
*/
function book_export($type, $nid) {
// Check that the node exists and that the current user has access to it.
$node = node_load($nid);
if (!$node) {
return MENU_NOT_FOUND;
}
if (!node_access('view', $node)) {
return MENU_ACCESS_DENIED;
}
$type = drupal_strtolower($type);
$export_function = 'book_export_' . $type;
if (function_exists($export_function)) {
print call_user_func($export_function, $nid);
}
else {
drupal_set_message(t('Unknown export format.'));
drupal_not_found();
}
}
/**
* 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 $nid
* An integer representing the node id (nid) of the node to export.
*
* @return
* A string containing HTML representing the node and its children in
* the book hierarchy.
*/
function book_export_html($nid) {
if (user_access('access printer-friendly version')) {
$node = node_load($nid);
if (isset($node->book)) {
$tree = book_menu_subtree_data($node->book);
$contents = book_export_traverse($tree, 'book_node_export');
return theme('book_export_html', array('title' => $node->title, 'contents' => $contents, 'depth' => $node->book['depth']));
}
else {
drupal_not_found();
}
}
else {
drupal_access_denied();
}
}
/**
* Menu callback: Shows the outline form for a single node.
*
* @param $node
* The book node for which to show the outline.
*
* @return string
* A HTML-formatted string with the outline form for a single node.
*
* @see book_menu()
*/
function book_outline($node) {
drupal_set_title($node->title);
return drupal_get_form('book_outline_form', $node);
}
/**
* Form constructor for the book outline form.
*
* Allows handling of all book outline operations via the outline tab.
*
* @param $node
* The book node for which to show the outline.
*
* @see book_outline_form_submit()
* @see book_remove_button_submit()
* @ingroup forms
*/
function book_outline_form($form, &$form_state, $node) {
if (!isset($node->book)) {
// The node is not part of any book yet - set default options.
$node->book = _book_link_defaults($node->nid);
}
else {
$node->book['original_bid'] = $node->book['bid'];
}
// Find the depth limit for the parent select.
if (!isset($node->book['parent_depth_limit'])) {
$node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book);
}
$form['#node'] = $node;
$form['#id'] = 'book-outline';
_book_add_form_elements($form, $form_state, $node);
$form['book']['#collapsible'] = FALSE;
$form['update'] = array(
'#type' => 'submit',
'#value' => $node->book['original_bid'] ? t('Update book outline') : t('Add to book outline'),
'#weight' => 15,
);
$form['remove'] = array(
'#type' => 'submit',
'#value' => t('Remove from book outline'),
'#access' => _book_node_is_removable($node),
'#weight' => 20,
'#submit' => array('book_remove_button_submit'),
);
return $form;
}
/**
* Form submission handler for book_outline_form().
*
* Redirects to removal confirmation form.
*
* @see book_outline_form_submit()
*/
function book_remove_button_submit($form, &$form_state) {
$form_state['redirect'] = 'node/' . $form['#node']->nid . '/outline/remove';
}
/**
* Form submission handler for book_outline_form().
*
* @see book_remove_button_submit()
*/
function book_outline_form_submit($form, &$form_state) {
$node = $form['#node'];
$form_state['redirect'] = "node/" . $node->nid;
$book_link = $form_state['values']['book'];
if (!$book_link['bid']) {
drupal_set_message(t('No changes were made'));
return;
}
$book_link['menu_name'] = book_menu_name($book_link['bid']);
$node->book = $book_link;
if (_book_update_outline($node)) {
if ($node->book['parent_mismatch']) {
// This will usually only happen when JS is disabled.
drupal_set_message(t('The post has been added to the selected book. You may now position it relative to other pages.'));
$form_state['redirect'] = "node/" . $node->nid . "/outline";
}
else {
drupal_set_message(t('The book outline has been updated.'));
}
}
else {
drupal_set_message(t('There was an error adding the post to the book.'), 'error');
}
}
/**
* Form constructor to confirm removal of a node from a book.
*
* @param $node
* The node to delete.
*
* @see book_remove_form_submit()
* @ingroup forms
*/
function book_remove_form($form, &$form_state, $node) {
$form['#node'] = $node;
$title = array('%title' => $node->title);
if ($node->book['has_children']) {
$description = 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 {
$description = t('%title may be added to hierarchy again using the Outline tab.', $title);
}
return confirm_form($form, t('Are you sure you want to remove %title from the book hierarchy?', $title), 'node/' . $node->nid, $description, t('Remove'));
}
/**
* Form submission handler for book_remove_form().
*/
function book_remove_form_submit($form, &$form_state) {
$node = $form['#node'];
if (_book_node_is_removable($node)) {
menu_link_delete($node->book['mlid']);
db_delete('book')
->condition('nid', $node->nid)
->execute();
drupal_set_message(t('The post has been removed from the book.'));
}
$form_state['redirect'] = 'node/' . $node->nid;
}

398
modules/book/book.test Normal file
View file

@ -0,0 +1,398 @@
<?php
/**
* @file
* Tests for book.module.
*/
/**
* Tests the functionality of the Book module.
*/
class BookTestCase extends DrupalWebTestCase {
/**
* A book node.
*
* @var object
*/
protected $book;
/**
* A user with permission to create and edit books.
*
* @var object
*/
protected $book_author;
/**
* A user with permission to view a book and access printer-friendly version.
*
* @var object
*/
protected $web_user;
/**
* A user with permission to create and edit books and to administer blocks.
*
* @var object
*/
protected $admin_user;
public static function getInfo() {
return array(
'name' => 'Book functionality',
'description' => 'Create a book, add pages, and test book interface.',
'group' => 'Book',
);
}
function setUp() {
parent::setUp(array('book', 'node_access_test'));
// node_access_test requires a node_access_rebuild().
node_access_rebuild();
// Create users.
$this->book_author = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books'));
$this->web_user = $this->drupalCreateUser(array('access printer-friendly version', 'node test view'));
$this->admin_user = $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'));
}
/**
* Creates a new book with a page hierarchy.
*/
function createBook() {
// Create new book.
$this->drupalLogin($this->book_author);
$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->nid); // Node 0.
$nodes[] = $this->createBookNode($book->nid, $nodes[0]->book['mlid']); // Node 1.
$nodes[] = $this->createBookNode($book->nid, $nodes[0]->book['mlid']); // Node 2.
$nodes[] = $this->createBookNode($book->nid); // Node 3.
$nodes[] = $this->createBookNode($book->nid); // Node 4.
$this->drupalLogout();
return $nodes;
}
/**
* Tests book functionality through node interfaces.
*/
function testBook() {
// Create new book.
$nodes = $this->createBook();
$book = $this->book;
$this->drupalLogin($this->web_user);
// 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();
// Create a second book, and move an existing book page into it.
$this->drupalLogin($this->book_author);
$other_book = $this->createBookNode('new');
$node = $this->createBookNode($book->nid);
$edit = array('book[bid]' => $other_book->nid);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->drupalLogout();
$this->drupalLogin($this->web_user);
// 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));
}
/**
* Checks the outline of sub-pages; previous, up, and next.
*
* Also checks the printer friendly version of the outline.
*
* @param $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 $breadcrumb
* The nodes that should be displayed in the breadcrumb.
*/
function checkBookNode($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->nid);
// 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) {
$this->assertRaw(l(' ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => array('page-previous'), 'title' => t('Go to previous page')))), 'Previous page link found.');
}
if ($up) {
$this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => array('page-up'), 'title' => t('Go to parent page')))), 'Up page link found.');
}
if ($next) {
$this->assertRaw(l($next->title . ' ', 'node/' . $next->nid, array('attributes' => array('class' => array('page-next'), 'title' => t('Go to next page')))), 'Next page link found.');
}
// Compute the expected breadcrumb.
$expected_breadcrumb = array();
$expected_breadcrumb[] = url('');
foreach ($breadcrumb as $a_node) {
$expected_breadcrumb[] = url('node/' . $a_node->nid);
}
// Fetch links in the current breadcrumb.
$links = $this->xpath('//div[@class="breadcrumb"]/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->nid);
$this->assertText($node->title, 'Printer friendly title found.');
$this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), '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
* A regular expression that locates sub-nodes of the outline.
*/
function generateOutlinePattern($nodes) {
$outline = '';
foreach ($nodes as $node) {
$outline .= '(node\/' . $node->nid . ')(.*?)(' . $node->title . ')(.*?)';
}
return '/<div id="book-navigation-' . $this->book->nid . '"(.*?)<ul(.*?)' . $outline . '<\/ul>/s';
}
/**
* Creates a book node.
*
* @param $book_nid
* A book node ID or set to 'new' to create a new book.
* @param $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();
$langcode = LANGUAGE_NONE;
$edit["title"] = $number . ' - SimpleTest test node ' . $this->randomName(10);
$edit["body[$langcode][0][value]"] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32);
$edit['book[bid]'] = $book_nid;
if ($parent !== NULL) {
$this->drupalPost('node/add/book', $edit, t('Change book (update list of parents)'));
$edit['book[plid]'] = $parent;
$this->drupalPost(NULL, $edit, t('Save'));
}
else {
$this->drupalPost('node/add/book', $edit, t('Save'));
}
// Check to make sure the book node was created.
$node = $this->drupalGetNodeByTitle($edit['title']);
$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->web_user);
$this->drupalGet('node/' . $this->book->nid);
$this->clickLink(t('Printer-friendly version'));
// Make sure each part of the book is there.
foreach ($nodes as $node) {
$this->assertText($node->title, 'Node title found in printer friendly version.');
$this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), 'Node body found in printer friendly version.');
}
// Make sure we can't export an unsupported format.
$this->drupalGet('book/export/foobar/' . $this->book->nid);
$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->nid);
$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->nid);
$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(DRUPAL_ANONYMOUS_RID, array('access printer-friendly version'));
$this->drupalGet('book/export/html/' . $this->book->nid);
$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->admin_user);
// Set block title to confirm that the interface is available.
$block_title = $this->randomName(16);
$this->drupalPost('admin/structure/block/manage/book/navigation/configure', array('title' => $block_title), t('Save block'));
$this->assertText(t('The block configuration has been saved.'), 'Block configuration set.');
// Set the block to a region to confirm block is available.
$edit = array();
$edit['blocks[book_navigation][region]'] = 'footer';
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
$this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.');
// Give anonymous users the permission 'node test view'.
$edit = array();
$edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE;
$this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $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_title, 'Book navigation block is displayed.');
$this->assertText($this->book->title, format_string('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->title)));
$this->assertNoText($nodes[0]->title, 'No links to individual book pages are displayed.');
}
/**
* Tests the book navigation block when an access module is enabled.
*/
function testNavigationBlockOnAccessModuleEnabled() {
$this->drupalLogin($this->admin_user);
$edit = array();
// Set the block title.
$block_title = $this->randomName(16);
$edit['title'] = $block_title;
// Set block display to 'Show block only on book pages'.
$edit['book_block_mode'] = 'book pages';
$this->drupalPost('admin/structure/block/manage/book/navigation/configure', $edit, t('Save block'));
$this->assertText(t('The block configuration has been saved.'), 'Block configuration set.');
// Set the block to a region to confirm block is available.
$edit = array();
$edit['blocks[book_navigation][region]'] = 'footer';
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
$this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.');
// Give anonymous users the permission 'node test view'.
$edit = array();
$edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE;
$this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $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->web_user);
$this->drupalGet('node/' . $this->book->nid);
$this->assertText($block_title, 'Book navigation block is displayed to registered users.');
$this->drupalLogout();
// Test correct display of the block to anonymous users.
$this->drupalGet('node/' . $this->book->nid);
$this->assertText($block_title, 'Book navigation block is displayed to anonymous users.');
}
/**
* Tests the access for deleting top-level book nodes.
*/
function testBookDelete() {
$nodes = $this->createBook();
$this->drupalLogin($this->admin_user);
$edit = array();
// Test access to delete top-level and child book nodes.
$this->drupalGet('node/' . $this->book->nid . '/outline/remove');
$this->assertResponse('403', 'Deleting top-level book node properly forbidden.');
$this->drupalPost('node/' . $nodes[4]->nid . '/outline/remove', $edit, t('Remove'));
$node4 = node_load($nodes[4]->nid, NULL, TRUE);
$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->nid;
}
node_delete_multiple($nids);
$this->drupalPost('node/' . $this->book->nid . '/outline/remove', $edit, t('Remove'));
$node = node_load($this->book->nid, NULL, TRUE);
$this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.');
}
}