Newer
Older
// $Id$
Dries Buytaert
committed
* Allows users to create and organize related content in an outline.
Dries Buytaert
committed
/**
* Implementation of hook_theme().
Dries Buytaert
committed
*/
function book_theme() {
return array(
'book_navigation' => array(
'arguments' => array('book_link' => NULL),
'template' => 'book-navigation',
Dries Buytaert
committed
),
'book_export_html' => array(
'arguments' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL),
'template' => 'book-export-html',
Dries Buytaert
committed
),
'book_admin_table' => array(
'arguments' => array('form' => NULL),
),
'book_title_link' => array(
'arguments' => array('link' => NULL),
),
'book_all_books_block' => array(
'arguments' => array('book_menus' => array()),
'template' => 'book-all-books-block',
),
'book_node_export_html' => array(
'arguments' => array('node' => NULL, 'children' => NULL),
'template' => 'book-node-export-html',
Dries Buytaert
committed
);
}
Dries Buytaert
committed
/**
* Implementation of hook_perm().
*/
return array(
Angie Byron
committed
'administer book outlines' => array(
'title' => t('Administer book outlines'),
'description' => t('Manage books through the administration panel.'),
),
'create new books' => array(
'title' => t('Create new books'),
'description' => t('Add new top-level books.'),
),
'add content to books' => array(
'title' => t('Add content to books'),
'description' => t('Add new content and child pages to books.'),
),
'access printer-friendly version' => array(
'title' => t('Access printer-friendly version'),
'description' => t('View a book page and all of its sub-pages as a single document for ease of printing. Can be performance heavy.'),
),
);
* Inject links into $node as needed.
function book_nodeapi_view_link($node, $teaser, $page) {
if (isset($node->book['depth'])) {
Neil Drumm
committed
if (!$teaser) {
$child_type = variable_get('book_child_type', 'book');
if ((user_access('add content to books') || user_access('administer book outlines')) && node_access('create', $child_type) && $node->status == 1 && $node->book['depth'] < MENU_MAX_DEPTH) {
Dries Buytaert
committed
$links['book_add_child'] = array(
Steven Wittens
committed
'title' => t('Add child page'),
'href' => "node/add/" . str_replace('_', '-', $child_type),
'query' => "parent=" . $node->book['mlid'],
Dries Buytaert
committed
);
if (user_access('access printer-friendly version')) {
Dries Buytaert
committed
$links['book_printer'] = array(
Steven Wittens
committed
'title' => t('Printer-friendly version'),
'href' => 'book/export/html/' . $node->nid,
'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.'))
Dries Buytaert
committed
);
}
if (!empty($links)) {
$node->content['links']['book'] = array(
'#type' => 'node_links',
'#value' => $links,
);
}
Dries Buytaert
committed
function book_menu() {
$items['admin/content/book'] = array(
'title' => 'Books',
'description' => "Manage your site's book outlines.",
'page callback' => 'book_admin_overview',
'access arguments' => array('administer book outlines'),
Dries Buytaert
committed
);
$items['admin/content/book/list'] = array(
'title' => 'List',
Dries Buytaert
committed
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/content/book/settings'] = array(
'title' => 'Settings',
Dries Buytaert
committed
'page callback' => 'drupal_get_form',
'page arguments' => array('book_admin_settings'),
'access arguments' => array('administer site configuration'),
Dries Buytaert
committed
'type' => MENU_LOCAL_TASK,
'weight' => 8,
);
$items['admin/content/book/%node'] = array(
'title' => 'Re-order book pages and change titles',
'page callback' => 'drupal_get_form',
'page arguments' => array('book_admin_edit', 3),
'access callback' => '_book_outline_access',
'access arguments' => array(3),
'type' => MENU_CALLBACK,
);
Dries Buytaert
committed
$items['book'] = array(
'title' => 'Books',
Dries Buytaert
committed
'page callback' => 'book_render',
'access arguments' => array('access content'),
'type' => MENU_SUGGESTED_ITEM,
);
$items['book/export/%/%'] = array(
'page callback' => 'book_export',
'page arguments' => array(2, 3),
'access arguments' => array('access printer-friendly version'),
Dries Buytaert
committed
'type' => MENU_CALLBACK,
);
$items['node/%node/outline'] = array(
'title' => 'Outline',
'page callback' => 'book_outline',
'page arguments' => array(1),
Dries Buytaert
committed
'access callback' => '_book_outline_access',
'access arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
);
$items['node/%node/outline/remove'] = array(
'title' => 'Remove from outline',
'page callback' => 'drupal_get_form',
'page arguments' => array('book_remove_form', 1),
'access callback' => '_book_outline_remove_access',
'access arguments' => array(1),
'type' => MENU_CALLBACK,
);
Gábor Hojtsy
committed
$items['book/js/form'] = array(
'page callback' => 'book_form_update',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
/**
* Menu item access callback - determine if the outline tab is accessible.
*/
Dries Buytaert
committed
function _book_outline_access($node) {
return user_access('administer book outlines') && node_access('view', $node);
}
/**
* Menu item access callback - determine if the user can remove nodes from the outline.
*/
function _book_outline_remove_access($node) {
return isset($node->book) && ($node->book['bid'] != $node->nid) && _book_outline_access($node);
Dries Buytaert
committed
}
* Implementation of hook_init().
Dries Buytaert
committed
function book_init() {
drupal_add_css(drupal_get_path('module', 'book') . '/book.css');
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implementation of hook_block_list().
*/
function book_block_list() {
$block = array();
$block['navigation']['info'] = t('Book navigation');
$block['navigation']['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE;
return $block;
}
/**
* Implementation of hook_block_view().
Dries Buytaert
committed
*
* Displays the book table of contents in a block when the current page is a
* single-node view of a book node.
Dries Buytaert
committed
*/
Dries Buytaert
committed
function book_block_view($delta = '') {
Dries Buytaert
committed
$current_bid = 0;
if ($node = menu_get_object()) {
$current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
}
Dries Buytaert
committed
if (variable_get('book_block_mode', 'all pages') == 'all pages') {
$block['subject'] = t('Book navigation');
$book_menus = array();
$pseudo_tree = array(0 => array('below' => FALSE));
foreach (book_get_books() 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.
$book_menus[$book_id] = menu_tree_output(menu_tree_all_data($node->book['menu_name'], $node->book));
Dries Buytaert
committed
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;
$pseudo_tree[0]['link'] = $book;
$book_menus[$book_id] = menu_tree_output($pseudo_tree);
Dries Buytaert
committed
}
$block['content'] = theme('book_all_books_block', $book_menus);
}
elseif ($current_bid) {
// Only display this block when the user is browsing a book.
$select = db_select('node');
$select->addField('node', 'title');
$select->condition('nid', $node->book['bid']);
$select->addTag('node_access');
$title = $select->execute()->fetchField();
// Only show the block if the user has view access for the top-level node.
if ($title) {
$tree = menu_tree_all_data($node->book['menu_name'], $node->book);
// There should only be one element at the top level.
$data = array_shift($tree);
$block['subject'] = theme('book_title_link', $data['link']);
$block['content'] = ($data['below']) ? menu_tree_output($data['below']) : '';
}
}
Dries Buytaert
committed
return $block;
}
Dries Buytaert
committed
/**
* Implementation of hook_block_configure().
*/
function book_block_configure($delta = '') {
$block = array();
$options = array(
'all pages' => t('Show block on all pages'),
'book pages' => t('Show block only on book pages'),
);
$form['book_block_mode'] = array(
'#type' => 'radios',
'#title' => t('Book navigation block display'),
'#options' => $options,
'#default_value' => variable_get('book_block_mode', 'all pages'),
'#description' => 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."),
);
Dries Buytaert
committed
return $form;
}
Dries Buytaert
committed
/**
* Implementation of hook_block_save().
*/
function book_block_save($delta = '', $edit = array()) {
$block = array();
variable_set('book_block_mode', $edit['book_block_mode']);
Dries Buytaert
committed
/**
* Generate the HTML output for a link to a book title when used as a block title.
*
* @ingroup themeable
Dries Buytaert
committed
*/
function theme_book_title_link($link) {
$link['options']['attributes']['class'] = 'book-title';
return l($link['title'], $link['href'], $link['options']);
Dries Buytaert
committed
/**
* 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.
Dries Buytaert
committed
*/
function book_get_books() {
static $all_books;
if (!isset($all_books)) {
$all_books = array();
Dries Buytaert
committed
$nids = db_query("SELECT DISTINCT(bid) FROM {book}")->fetchCol();
Dries Buytaert
committed
$query = db_select('book', 'b', array('fetch' => PDO::FETCH_ASSOC));
$node_alias = $query->join('node', 'n', 'b.nid = n.nid');
$menu_links_alias = $query->join('menu_links', 'ml', 'b.mlid = ml.mlid');
$query->addField('n', 'type', 'type');
$query->addField('n', 'title', 'title');
$query->fields('b');
$query->fields($menu_links_alias);
$query->condition('n.nid', $nids, 'IN');
$query->condition('n.status', 1);
$query->orderBy('ml.weight');
$query->orderBy('ml.link_title');
$query->addTag('node_access');
$result2 = $query->execute();
foreach ($result2 as $link) {
$link['href'] = $link['link_path'];
$link['options'] = unserialize($link['options']);
$all_books[$link['bid']] = $link;
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
}
* Implementation of hook_form_alter().
*
* Adds the book fieldset to the node form.
*
* @see book_pick_book_submit()
* @see book_submit()
*/
function book_form_alter(&$form, $form_state, $form_id) {
Dries Buytaert
committed
if (!empty($form['#node_edit_form'])) {
// Add elements to the node form.
$node = $form['#node'];
$access = user_access('administer book outlines');
if (!$access) {
if (user_access('add content to books') && ((!empty($node->book['mlid']) && !empty($node->nid)) || book_type_is_allowed($node->type))) {
// Already in the book hierarchy, or this node type is allowed.
$access = TRUE;
}
}
if ($access) {
_book_add_form_elements($form, $node);
$form['book']['pick-book'] = array(
'#type' => 'submit',
'#value' => t('Change book (update list of parents)'),
// Submit the node form so the parent select options get updated.
// This is typically only used when JS is disabled. Since the parent options
// won't be changed via AJAX, a button is provided in the node form to submit
// the form and generate options in the parent select corresponding to the
// selected book. This is similar to what happens during a node preview.
'#submit' => array('node_form_submit_build_node'),
Steven Wittens
committed
}
Dries Buytaert
committed
* Build the parent selection form element for the node form or outline tab.
*
* This function is also called when generating a new set of options during the
* AJAX callback, so an array is returned that can be used to replace an existing
* form element.
*/
function _book_parent_select($book_link) {
Gábor Hojtsy
committed
if (variable_get('menu_override_parent_selector', FALSE)) {
return array();
}
// Offer a message or a drop-down to choose a different parent page.
$form = array(
'#type' => 'hidden',
'#value' => -1,
'#prefix' => '<div id="edit-book-plid-wrapper">',
'#suffix' => '</div>',
);
if ($book_link['nid'] === $book_link['bid']) {
// This is a book - at the top level.
if ($book_link['original_bid'] === $book_link['bid']) {
$form['#prefix'] .= '<em>' . t('This is the top-level page in this book.') . '</em>';
$form['#prefix'] .= '<em>' . t('This will be the top-level page in this book.') . '</em>';
}
}
elseif (!$book_link['bid']) {
$form['#prefix'] .= '<em>' . t('No book selected.') . '</em>';
Dries Buytaert
committed
}
else {
$form = array(
'#type' => 'select',
'#title' => t('Parent item'),
'#default_value' => $book_link['plid'],
Dries Buytaert
committed
'#description' => t('The parent page in the book. The maximum depth for a book and all child pages is !maxdepth. Some pages in the selected book may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
Gábor Hojtsy
committed
'#options' => book_toc($book_link['bid'], array($book_link['mlid']), $book_link['parent_depth_limit']),
Gábor Hojtsy
committed
'#attributes' => array('class' => 'book-title-select'),
Dries Buytaert
committed
/**
* Build the common elements of the book form for the node and outline forms.
Dries Buytaert
committed
*/
function _book_add_form_elements(&$form, $node) {
// Need this for AJAX.
$form['#cache'] = TRUE;
Gábor Hojtsy
committed
drupal_add_js("if (Drupal.jsEnabled) { $(document).ready(function() { $('#edit-book-pick-book').css('display', 'none'); }); }", 'inline');
$form['book'] = array(
'#type' => 'fieldset',
'#title' => t('Book outline'),
'#weight' => 10,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#tree' => TRUE,
Gábor Hojtsy
committed
'#attributes' => array('class' => 'book-outline-form'),
Dries Buytaert
committed
);
Gábor Hojtsy
committed
foreach (array('menu_name', 'mlid', 'nid', 'router_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) {
$form['book'][$key] = array(
'#type' => 'value',
'#value' => $node->book[$key],
);
}
$form['book']['plid'] = _book_parent_select($node->book);
$form['book']['weight'] = array(
'#type' => 'weight',
Dries Buytaert
committed
'#title' => t('Weight'),
'#default_value' => $node->book['weight'],
Dries Buytaert
committed
'#delta' => 15,
Dries Buytaert
committed
'#description' => t('Pages at a given level are ordered first by weight and then by title.'),
);
$options = array();
$nid = isset($node->nid) ? $node->nid : 'new';
Gábor Hojtsy
committed
if (isset($node->nid) && ($nid == $node->book['original_bid']) && ($node->book['parent_depth_limit'] == 0)) {
Gábor Hojtsy
committed
// This is the top level node in a maximum depth book and thus cannot be moved.
$options[$node->nid] = $node->title;
}
else {
foreach (book_get_books() as $book) {
$options[$book['nid']] = $book['title'];
}
}
if (user_access('create new books') && ($nid == 'new' || ($nid != $node->book['original_bid']))) {
// The node can become a new book, if it is not one already.
$options = array($nid => '<' . t('create a new book') . '>') + $options;
// The node is not currently in the hierarchy.
$options = array(0 => '<' . t('none') . '>') + $options;
}
// Add a drop-down to select the destination book.
$form['book']['bid'] = array(
'#type' => 'select',
'#title' => t('Book'),
'#default_value' => $node->book['bid'],
'#options' => $options,
'#access' => (bool)$options,
'#description' => t('Your page will be a part of the selected book.'),
'#weight' => -5,
Gábor Hojtsy
committed
'#attributes' => array('class' => 'book-title-select'),
Gábor Hojtsy
committed
'#ahah' => array(
'path' => 'book/js/form',
'wrapper' => 'edit-book-plid-wrapper',
'effect' => 'slide',
),
Dries Buytaert
committed
);
Dries Buytaert
committed
/**
* Common helper function to handles additions and updates to the book outline.
* Performs all additions and updates to the book outline through node addition,
* node editing, node deletion, or the outline tab.
*/
function _book_update_outline(&$node) {
if (empty($node->book['bid'])) {
return FALSE;
}
$new = empty($node->book['mlid']);
$node->book['link_path'] = 'node/' . $node->nid;
$node->book['link_title'] = $node->title;
$node->book['parent_mismatch'] = FALSE; // The normal case.
if ($node->book['bid'] == $node->nid) {
$node->book['plid'] = 0;
$node->book['menu_name'] = book_menu_name($node->nid);
}
else {
// Check in case the parent is not is this book; the book takes precedence.
if (!empty($node->book['plid'])) {
Dries Buytaert
committed
$parent = db_query("SELECT * FROM {book} WHERE mlid = :mlid", array(
':mlid' => $node->book['plid'],
))->fetchAssoc();
}
if (empty($node->book['plid']) || !$parent || $parent['bid'] != $node->book['bid']) {
Dries Buytaert
committed
$node->book['plid'] = db_query("SELECT mlid FROM {book} WHERE nid = :nid", array(
':nid' => $node->book['bid'],
))->fetchField();
$node->book['parent_mismatch'] = TRUE; // Likely when JS is disabled.
}
}
if (menu_link_save($node->book)) {
if ($new) {
// Insert new.
Dries Buytaert
committed
db_insert('book')
->fields(array(
'nid' => $node->nid,
'mlid' => $node->book['mlid'],
'bid' => $node->book['bid'],
))
->execute();
Dries Buytaert
committed
if ($node->book['bid'] != db_query("SELECT bid FROM {book} WHERE nid = :nid", array(
':nid' => $node->nid,
))->fetchField()) {
// Update the bid for this page and all children.
book_update_bid($node->book);
}
}
// Failed to save the menu link.
* Update the bid for a page and its children when it is moved to a new book.
* @param $book_link
* A fully loaded menu link that is part of the book hierarchy.
*/
function book_update_bid($book_link) {
Dries Buytaert
committed
$query = db_select('menu_links');
$query->addField('menu_links', 'mlid');
for ($i = 1; $i <= MENU_MAX_DEPTH && $book_link["p$i"]; $i++) {
Dries Buytaert
committed
$query->condition("p$i", $book_link["p$i"]);
Dries Buytaert
committed
$mlids = $query->execute()->fetchCol();
Dries Buytaert
committed
db_update('book')
->fields(array('bid', $book_link['bid']))
->condition('mlid', $mlids, 'IN')
->execute();
}
}
/**
* Get the book menu tree for a page, and return it as a linear array.
* @param $book_link
* A fully loaded menu link that is part of the book hierarchy.
* A linear array of menu links in the order that the links are shown in the
* menu, so the previous and next pages are the elements before and after the
Dries Buytaert
committed
* element corresponding to $node. The children of $node (if any) will come
* immediately after it in the array.
function book_get_flat_menu($book_link) {
static $flat = array();
if (!isset($flat[$book_link['mlid']])) {
// Call menu_tree_all_data() to take advantage of the menu system's caching.
$tree = menu_tree_all_data($book_link['menu_name'], $book_link);
$flat[$book_link['mlid']] = array();
_book_flatten_menu($tree, $flat[$book_link['mlid']]);
Dries Buytaert
committed
/**
* Recursive helper function for book_get_flat_menu().
Dries Buytaert
committed
*/
function _book_flatten_menu($tree, &$flat) {
foreach ($tree as $data) {
if (!$data['link']['hidden']) {
$flat[$data['link']['mlid']] = $data['link'];
if ($data['below']) {
_book_flatten_menu($data['below'], $flat);
}
}
/**
* Fetches the menu link for the previous page of the book.
*/
function book_prev($book_link) {
// If the parent is zero, we are at the start of a book.
if ($book_link['plid'] == 0) {
return NULL;
$flat = book_get_flat_menu($book_link);
// Assigning the array to $flat resets the array pointer for use with each().
$curr = NULL;
do {
$prev = $curr;
list($key, $curr) = each($flat);
} while ($key && $key != $book_link['mlid']);
if ($key == $book_link['mlid']) {
// The previous page in the book may be a child of the previous visible link.
if ($prev['depth'] == $book_link['depth'] && $prev['has_children']) {
// The subtree will have only one link at the top level - get its data.
$tree = book_menu_subtree_data($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']);
}
return $data['link'];
}
else {
return $prev;
}
Dries Buytaert
committed
/**
* Fetches the menu link for the next page of the book.
Dries Buytaert
committed
*/
function book_next($book_link) {
$flat = book_get_flat_menu($book_link);
// Assigning the array to $flat resets the array pointer for use with each().
do {
list($key, $curr) = each($flat);
}
while ($key && $key != $book_link['mlid']);
if ($key == $book_link['mlid']) {
return current($flat);
/**
* Format the menu links for the child pages of the current page.
*/
function book_children($book_link) {
$flat = book_get_flat_menu($book_link);
Dries Buytaert
committed
$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['mlid'] != $book_link['mlid']));
// Continue though the array and collect the links whose parent is this page.
while (($link = array_shift($flat)) && $link['plid'] == $book_link['mlid']) {
$data['link'] = $link;
$data['below'] = '';
$children[] = $data;
return $children ? menu_tree_output($children) : '';
* Generate the corresponding menu name from a book ID.
return 'book-toc-' . $bid;
}
/**
* Build an active trail to show in the breadcrumb.
*/
function book_build_active_trail($book_link) {
static $trail;
if (!isset($trail)) {
$trail = array();
Dries Buytaert
committed
$trail[] = array('title' => t('Home'), 'href' => '<front>', 'localized_options' => array());
$tree = menu_tree_all_data($book_link['menu_name'], $book_link);
$curr = array_shift($tree);
while ($curr) {
if ($curr['link']['href'] == $book_link['href']) {
$trail[] = $curr['link'];
$curr = FALSE;
}
else {
if ($curr['below'] && $curr['link']['in_active_trail']) {
$trail[] = $curr['link'];
$tree = $curr['below'];
}
$curr = array_shift($tree);
}
}
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implementation of hook_nodeapi_load().
Dries Buytaert
committed
*/
function book_nodeapi_load($nodes, $types) {
$result = db_query("SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid IN (" . db_placeholders(array_keys($nodes)) . ")", array_keys($nodes), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $record) {
$nodes[$record['nid']]->book = $record;
$nodes[$record['nid']]->book['href'] = $record['link_path'];
$nodes[$record['nid']]->book['title'] = $record['link_title'];
$nodes[$record['nid']]->book['options'] = unserialize($record['options']);
Dries Buytaert
committed
}
}
Dries Buytaert
committed
/**
* Implementation of hook_nodeapi_view().
*/
Dries Buytaert
committed
function book_nodeapi_view($node, $teaser, $page) {
Dries Buytaert
committed
if (!$teaser) {
if (!empty($node->book['bid']) && $node->build_mode == NODE_BUILD_NORMAL) {
$node->content['book_navigation'] = array(
'#markup' => theme('book_navigation', $node->book),
'#weight' => 100,
);
Dries Buytaert
committed
if ($page) {
menu_set_active_trail(book_build_active_trail($node->book));
menu_set_active_menu_name($node->book['menu_name']);
Dries Buytaert
committed
}
}
book_nodeapi_view_link($node, $teaser, $page);
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Implementation of hook_nodeapi_presave().
*/
Dries Buytaert
committed
function book_nodeapi_presave($node) {
Dries Buytaert
committed
// Always save a revision for non-administrators.
if (!empty($node->book['bid']) && !user_access('administer nodes')) {
$node->revision = 1;
}
// Make sure a new node gets a new menu link.
if (empty($node->nid)) {
$node->book['mlid'] = NULL;
}
}
Dries Buytaert
committed
/**
* Implementation of hook_nodeapi_insert().
*/
Dries Buytaert
committed
function book_nodeapi_insert($node) {
Dries Buytaert
committed
if (!empty($node->book['bid'])) {
if ($node->book['bid'] == 'new') {
// New nodes that are their own book.
$node->book['bid'] = $node->nid;
}
$node->book['nid'] = $node->nid;
$node->book['menu_name'] = book_menu_name($node->book['bid']);
_book_update_outline($node);
}
}
Dries Buytaert
committed
/**
* Implementation of hook_nodeapi_update().
*/
Dries Buytaert
committed
function book_nodeapi_update($node) {
Dries Buytaert
committed
if (!empty($node->book['bid'])) {
if ($node->book['bid'] == 'new') {
// New nodes that are their own book.
$node->book['bid'] = $node->nid;
}
$node->book['nid'] = $node->nid;
$node->book['menu_name'] = book_menu_name($node->book['bid']);
_book_update_outline($node);
}
}
Dries Buytaert
committed
/**
* Implementation of hook_nodeapi_delete().
*/
Dries Buytaert
committed
function book_nodeapi_delete($node) {
Dries Buytaert
committed
if (!empty($node->book['bid'])) {
if ($node->nid == $node->book['bid']) {
// Handle deletion of a top-level post.
Dries Buytaert
committed
$result = db_query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = :plid", array(
':plid' => $node->book['mlid']
));
foreach ($result as $child) {
$child_node = node_load($child->nid);
Dries Buytaert
committed
$child_node->book['bid'] = $child_node->nid;
_book_update_outline($child_node);
Dries Buytaert
committed
}
menu_link_delete($node->book['mlid']);
Dries Buytaert
committed
db_delete('book')
->condition('mlid', $node->book['mlid'])
->execute();
Dries Buytaert
committed
}
}
/**
* Implementation of hook_nodeapi_prepare().
*/
Dries Buytaert
committed
function book_nodeapi_prepare($node) {
Dries Buytaert
committed
// Prepare defaults for the add/edit form.
if (empty($node->book) && (user_access('add content to books') || user_access('administer book outlines'))) {
$node->book = array();
if (empty($node->nid) && isset($_GET['parent']) && is_numeric($_GET['parent'])) {
// Handle "Add child page" links:
$parent = book_link_load($_GET['parent']);
if ($parent && $parent['access']) {
$node->book['bid'] = $parent['bid'];
$node->book['plid'] = $parent['mlid'];
$node->book['menu_name'] = $parent['menu_name'];
Gábor Hojtsy
committed
}
Dries Buytaert
committed
}
// Set defaults.
$node->book += _book_link_defaults(!empty($node->nid) ? $node->nid : 'new');
}
else {
if (isset($node->book['bid']) && !isset($node->book['original_bid'])) {
$node->book['original_bid'] = $node->book['bid'];
}
}
// Find the depth limit for the parent select.
if (isset($node->book['bid']) && !isset($node->book['parent_depth_limit'])) {
$node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book);
Gábor Hojtsy
committed
/**
* Find the depth limit for items in the parent select.
*/
function _book_parent_depth_limit($book_link) {
return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? menu_link_children_relative_depth($book_link) : 0);
}
/**
* Form altering function for the confirm form for a single node deletion.
*/
function book_form_node_delete_confirm_alter(&$form, $form_state) {
$node = node_load($form['nid']['#value']);
if (isset($node->book) && $node->book['has_children']) {
$form['book_warning'] = array(
'#markup' => '<p>' . t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->title)) . '</p>',
Dries Buytaert
committed
/**
* Return an array with default values for a book link.
*/
function _book_link_defaults($nid) {
return array('original_bid' => 0, 'menu_name' => '', 'nid' => $nid, 'bid' => 0, 'router_path' => 'node/%', 'plid' => 0, 'mlid' => 0, 'has_children' => 0, 'weight' => 0, 'module' => 'book', 'options' => array());
}
/**
* Process variables for book-navigation.tpl.php.
* The $variables array contains the following arguments:
* - $book_link
* @see book-navigation.tpl.php
Dries Buytaert
committed
*/
function template_preprocess_book_navigation(&$variables) {
$book_link = $variables['book_link'];
// Provide extra variables for themers. Not needed by default.
$variables['book_id'] = $book_link['bid'];
$variables['book_title'] = check_plain($book_link['link_title']);
$variables['book_url'] = 'node/' . $book_link['bid'];
$variables['current_depth'] = $book_link['depth'];
$variables['tree'] = '';
$variables['tree'] = book_children($book_link);
if ($prev = book_prev($book_link)) {
$prev_href = url($prev['href']);
drupal_add_link(array('rel' => 'prev', 'href' => $prev_href));
$variables['prev_url'] = $prev_href;
$variables['prev_title'] = check_plain($prev['title']);
if ($book_link['plid'] && $parent = book_link_load($book_link['plid'])) {
$parent_href = url($parent['href']);
drupal_add_link(array('rel' => 'up', 'href' => $parent_href));
$variables['parent_url'] = $parent_href;
$variables['parent_title'] = check_plain($parent['title']);
if ($next = book_next($book_link)) {
$next_href = url($next['href']);
drupal_add_link(array('rel' => 'next', 'href' => $next_href));
$variables['next_url'] = $next_href;
$variables['next_title'] = check_plain($next['title']);
$variables['has_links'] = FALSE;
// Link variables to filter for values and set state of the flag variable.
$links = array('prev_url', 'prev_title', 'parent_url', 'parent_title', 'next_url', 'next_title');
foreach ($links as $link) {
if (isset($variables[$link])) {
// Flag when there is a value.
$variables['has_links'] = TRUE;
}
else {
// Set empty to prevent notices.
$variables[$link] = '';
Dries Buytaert
committed
}
* A recursive helper function for book_toc().
Gábor Hojtsy
committed
function _book_toc_recurse($tree, $indent, &$toc, $exclude, $depth_limit) {
Gábor Hojtsy
committed
if ($data['link']['depth'] > $depth_limit) {
// Don't iterate through any links on this level.
break;
}
if (!in_array($data['link']['mlid'], $exclude)) {
$toc[$data['link']['mlid']] = $indent . ' ' . truncate_utf8($data['link']['title'], 30, TRUE, TRUE);
Gábor Hojtsy
committed
if ($data['below']) {
_book_toc_recurse($data['below'], $indent . '--', $toc, $exclude, $depth_limit);
Steven Wittens
committed
}
* Returns an array of book pages in table of contents order.
*
* @param $bid
* The ID of the book whose pages are to be listed.
* @param $exclude
Dries Buytaert
committed
* Optional array of mlid values. Any link whose mlid is in this array
* will be excluded (along with its children).
Gábor Hojtsy
committed
* @param $depth_limit
* Any link deeper than this value will be excluded (along with its children).
* @return
* An array of mlid, title pairs for use as options for selecting a book page.
Gábor Hojtsy
committed
function book_toc($bid, $exclude = array(), $depth_limit) {
$tree = menu_tree_all_data(book_menu_name($bid));
$toc = array();
Gábor Hojtsy
committed
_book_toc_recurse($tree, '', $toc, $exclude, $depth_limit);
/**
* Process variables for book-export-html.tpl.php.
*
* The $variables array contains the following arguments:
* - $title
* - $contents
* - $depth
*
* @see book-export-html.tpl.php
*/
function template_preprocess_book_export_html(&$variables) {
global $base_url, $language;
$variables['title'] = check_plain($variables['title']);
$variables['base_url'] = $base_url;
$variables['language'] = $language;
Dries Buytaert
committed
$variables['language_rtl'] = defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL;
$variables['head'] = drupal_get_html_head();
}