Book Module: How to make a book navigation block appear on all pages.
description
This snippet will create a book navigation menu that can be present on your site any time, not just when navigating a book. It has three configurable features:
$book_top_pageis the node number which represents the top level of your book. Since Drupal and the book module support multiple books, you must supply the desired node number for the script to follow.$levels_deepis the number of levels you want to show in the initial navigation. "0" will result in nothing being shown. "1" will show one level, "2" will show two levels deep, etc.$emulate_book_blockdetermines whether or not the code will emulate the book menu. For example, if$emulate_book_blockis set toFALSE, then the menu will not expand to show child pages beyond the number of levels specified by$levels_deep. If$emulate_book_blockis set toTRUE, then the menu will expand to show the necessary pages.
To behave exactly like the book block, set $levels_deep to "1", and $emulate_book_block to TRUE.
usage
If you put this into a custom block, be sure to disable the default book block. Nothing bad will happen if you don't, but it might look odd to have two book navigation menus showing at the same time!
Placed in a page, this could easily provide a complete look at a book's entire hierarchy.
<?php
$book_top_page = 159;
$levels_deep = 2;
$emulate_book_block = true;
if (!function_exists('book_struct_recurse')){
function book_struct_recurse($nid, $levels_deep, $children, $current_lineage = array(), $emulate_book_block = true) {
$struct = '';
if ($children[$nid] && ($levels_deep > 0 || ($emulate_book_block && in_array($nid, $current_lineage)))) {
$struct = '<ul>';
foreach ($children[$nid] as $key => $node) {
if ($tree = book_struct_recurse($node->nid, $levels_deep - 1, $children, $current_lineage, $emulate_book_block)) {
$struct .= '<li class="expanded">';
$struct .= l($node->title, 'node/'. $node->nid);
$struct .= $tree;
$struct .= '</li>';
}
else {
if ($children[$node->nid]){
$struct .= '<li class="collapsed">'. l($node->title, 'node/'. $node->nid) .'</li>';
}
else {
$struct .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid) .'</li>';
}
}
}
$struct .= '</ul>';
return $struct;
}
}
}
$current_lineage = array();
// use this version for Drupal 4.6
// $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.status = 1 ORDER BY b.weight, n.title'));
// use this version for Drupal 4.7 and Drupal 5
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid AND n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title'));
while ($node = db_fetch_object($result)) {
if (!$children[$node->parent]) {
$children[$node->parent] = array();
}
array_push($children[$node->parent], $node);
if (arg(0) == 'node' && is_numeric(arg(1)) && arg(1) == $node->nid) {
$_temp = book_location($node);
foreach ($_temp as $key => $val){
$current_lineage[] = $val->nid;
}
$current_lineage[] = arg(1);
}
}
echo book_struct_recurse($book_top_page, $levels_deep, $children, $current_lineage, $emulate_book_block);
?>Note: this snippet may not quite display correctly in Garland (Drupal 5.x).
Changing line 10 $struct = '<ul>'; to $struct = '<ul class="menu">'; fixes the problem.

Is this right for adding the block to all pages?
I put the suggested code into a custom block which I hoped to use for navigation; but it seems the block only shows up on the book pages. Removing this bit seemed to solve the problem? Is there a better way?
<?phpif (arg(0) == 'node' && is_numeric(arg(1)) && arg(1) == $node->nid) {
$_temp = book_location($node);
foreach ($_temp as $key => $val){
$current_lineage[] = $val->nid;
}
$current_lineage[] = arg(1);
}
?>
(I'm a rank amateur, so don't try this at home!)
Efficiency in SQL query
The sql query in this block will return *all* book nodes for php processing. If you have hundreds/thousands of book nodes, this will lead to all book nodes being processed by the interpreted PHP (not good). Since you've already got the limiting nodeid listed as book_top_page, I think it makes sense to limit the sql query to only returning those nodes which have book_top_page as their parent, e.g.:
...
$bookNodeQuery = 'SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid AND n.vid = b.vid AND b.parent = ' . $book_top_page . ' WHERE n.status = 1 ORDER BY b.weight, n.title';
$result = db_query(db_rewrite_sql($bookNodeQuery));
...
This adds another benefit, that you can now limit the returned set as well inside the SQL query:
...
$bookNodeQuery = 'SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid AND n.vid = b.vid AND b.parent = ' . $book_top_page . ' WHERE n.status = 1 ORDER BY b.weight, n.title LIMIT 5';
$result = db_query(db_rewrite_sql($bookNodeQuery));
...
And so on...
Query change
$bookNodeQuery = 'SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid AND n.vid = b.vid AND b.parent = ' . $book_top_page . ' WHERE n.status = 1 ORDER BY b.weight, n.title';
$result = db_query(db_rewrite_sql($bookNodeQuery));
This allows for injection and should be replaced with the following:
$bookNodeQuery = 'SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid AND n.vid = b.vid AND b.parent = %d WHERE n.status = 1 ORDER BY b.weight, n.title';
$result = db_query($bookNodeQuery,$book_top_page);
Minor thing but helps keep things closer to standards, esp. since db_query requires this in 6.x and recommends it in 5.x
Display problem also in Marinelli
Thank you for this useful snippet. Publishing the sections of a site as a book is much more efficient (for a web site editor) than using the 'page' content type.
As for the line 10 fix, it also applies to the Marinelli theme (http://drupal.org/project/marinelli).
If you don't care about recursion...
In Drupal 5, if all you really want is a list of the top-level sub-pages of a particular book, you can just do this:
<?phpprint book_tree(123);
?>
(where 123 is the node ID of the top-level book page.)