Newer
Older
// $Id$
Dries Buytaert
committed
/**
* Implementation of hook_node_info().
Dries Buytaert
committed
*/
function book_node_info() {
return array('book' => array('name' => t('book page'), 'base' => 'book'));
Dries Buytaert
committed
/**
* Implementation of hook_perm().
*/
return array('outline posts in books', 'create book pages', 'create new books', 'edit book pages', 'edit own book pages', 'see printer-friendly version');
Dries Buytaert
committed
/**
* Implementation of hook_access().
*/
Dries Buytaert
committed
if ($op == 'create') {
// Only registered users can create book pages. Given the nature
return user_access('create book pages');
Dries Buytaert
committed
if ($op == 'update') {
// Only registered users can update book pages. Given the nature
// of the book module this is considered to be a good/safe idea.
// One can only update a book page if there are no suggested updates
// of that page waiting for approval. That is, only updates that
// don't overwrite the current or pending information are allowed.
Steven Wittens
committed
if ((user_access('edit book pages') && !$node->moderate) || ($node->uid == $user->uid && user_access('edit own book pages'))) {
Dries Buytaert
committed
return TRUE;
}
else {
// do nothing. node-access() will determine further access
}
Dries Buytaert
committed
if ($type == 'node' && isset($node->parent)) {
Dries Buytaert
committed
$links['book_add_child'] = array(
'title' => t('add child page'),
'href' => "node/add/book/parent/$node->nid"
Dries Buytaert
committed
);
if (user_access('see printer-friendly version')) {
Dries Buytaert
committed
$links['book_printer'] = array(
'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
);
}
Dries Buytaert
committed
$items[] = array(
'path' => 'node/add/book',
'title' => t('book page'),
'access' => user_access('create book pages'));
Dries Buytaert
committed
$items[] = array(
'path' => 'admin/node/book',
'title' => t('books'),
'callback' => 'book_admin',
'access' => user_access('administer nodes'),
'type' => MENU_LOCAL_TASK,
'weight' => -1);
$items[] = array(
'path' => 'admin/node/book/list',
'title' => t('list'),
'type' => MENU_DEFAULT_LOCAL_TASK);
Dries Buytaert
committed
$items[] = array(
'path' => 'admin/node/book/orphan',
'title' => t('orphan pages'),
'type' => MENU_LOCAL_TASK,
$items[] = array(
'path' => 'book',
'title' => t('books'),
'callback' => 'book_render',
'access' => user_access('access content'),
'type' => MENU_SUGGESTED_ITEM);
Dries Buytaert
committed
'path' => 'book/export',
'callback' => 'book_export',
'access' => user_access('access content'),
else {
// To avoid SQL overhead, check whether we are on a node page and whether the
// user is allowed to outline posts in books.
if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('outline posts in books')) {
$result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.nid = %d AND n.type != 'book'"), arg(1));
$items[] = array(
'path' => 'node/'. arg(1) .'/outline',
'title' => t('outline'),
'callback' => 'book_outline',
Dries Buytaert
committed
'callback arguments' => array(arg(1)),
'access' => user_access('outline posts in books'),
'type' => MENU_LOCAL_TASK,
'weight' => 2);
Dries Buytaert
committed
/**
* Implementation of hook_block().
*
* 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
*/
return $block;
else if ($op == 'view') {
// Only display this block when the user is browsing a book:
if (arg(0) == 'node' && is_numeric(arg(1))) {
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), arg(1));
if (db_num_rows($result) > 0) {
$node = db_fetch_object($result);
$path = book_location($node);
$path[] = $node;
$expand = array();
foreach ($path as $key => $node) {
$expand[] = $node->nid;
}
$block['subject'] = check_plain($path[0]->title);
return $block;
}
Dries Buytaert
committed
/**
* Implementation of hook_load().
*/
return db_fetch_object(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
Dries Buytaert
committed
/**
* Implementation of hook_insert().
*/
db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
Dries Buytaert
committed
/**
* Implementation of hook_update().
*/
if ($node->revision) {
db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
}
else {
db_query("UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d", $node->parent, $node->weight, $node->vid);
}
Dries Buytaert
committed
/**
* Implementation of hook_delete().
*/
Kjartan Mannes
committed
function book_delete(&$node) {
Dries Buytaert
committed
db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implementation of hook_submit().
Dries Buytaert
committed
*/
Dries Buytaert
committed
function book_submit(&$node) {
Dries Buytaert
committed
// Set default values for non-administrators.
if (!user_access('administer nodes')) {
$node->weight = 0;
$node->revision = 1;
$book->uid = $user->uid;
$book->name = $user->uid ? $user->name : '';
Dries Buytaert
committed
/**
* Implementation of hook_form().
*/
Steven Wittens
committed
if ($node->nid && !$node->parent && !user_access('create new books')) {
$form['parent'] = array('#type' => 'value', '#value' => $node->parent);
Steven Wittens
committed
}
else {
$form['parent'] = array('#type' => 'select',
'#title' => t('Parent'),
'#default_value' => ($node->parent ? $node->parent : arg(4)),
'#options' => book_toc($node->nid),
'#weight' => -4,
'#description' => user_access('create new books') ? t('The parent section in which to place this page. Note that each page whose parent is <top-level> is an independent, top-level book.') : t('The parent that this page belongs in.'),
Steven Wittens
committed
);
}
Dries Buytaert
committed
$form['title'] = array('#type' => 'textfield',
'#title' => t('Title'),
'#required' => TRUE,
'#default_value' => $node->title,
'#weight' => -5,
);
$form['body_filter']['body'] = array('#type' => 'textarea',
'#title' => t('Body'),
'#default_value' => $node->body,
'#rows' => 20,
'#required' => TRUE,
);
$form['body_filter']['format'] = filter_form($node->format);
Dries Buytaert
committed
'#type' => 'textarea',
'#title' => t('Log message'),
'#default_value' => $node->log,
'#weight' => 5,
'#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.'),
Dries Buytaert
committed
if (user_access('administer nodes')) {
Dries Buytaert
committed
$form['weight'] = array('#type' => 'weight',
'#title' => t('Weight'),
'#default_value' => $node->weight,
'#delta' => 15,
'#weight' => 5,
'#description' => t('Pages at a given level are ordered first by weight and then by title.'),
// If a regular user updates a book page, we create a new revision
// authored by that user:
Dries Buytaert
committed
$form['revision'] = array('#type' => 'hidden', '#value' => 1);
Dries Buytaert
committed
/**
* Implementation of function book_outline()
* Handles all book outline operations.
Dries Buytaert
committed
*/
Dries Buytaert
committed
function book_outline($nid) {
$node = node_load($nid);
Gerhard Killesreiter
committed
$page = book_load($node);
Dries Buytaert
committed
$form['parent'] = array('#type' => 'select',
'#title' => t('Parent'),
'#default_value' => $page->parent,
Steven Wittens
committed
'#options' => book_toc($node->nid),
Dries Buytaert
committed
'#description' => t('The parent page in the book.'),
);
$form['weight'] = array('#type' => 'weight',
'#title' => t('Weight'),
'#default_value' => $page->weight,
'#delta' => 15,
'#description' => t('Pages at a given level are ordered first by weight and then by title.'),
);
$form['log'] = array('#type' => 'textarea',
'#title' => t('Log message'),
'#default_value' => $node->log,
'#description' => t('An explanation to help other authors understand your motivations to put this post into the book.'),
);
Dries Buytaert
committed
$form['nid'] = array('#type' => 'value', '#value' => $nid);
if ($page->nid) {
$form['update'] = array('#type' => 'submit',
'#value' => t('Update book outline'),
);
$form['remove'] = array('#type' => 'submit',
'#value' => t('Remove from book outline'),
);
}
else {
$form['add'] = array('#type' => 'submit', '#value' => t('Add to book outline'));
}
Dries Buytaert
committed
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
drupal_set_title(check_plain($node->title));
return drupal_get_form('book_outline', $form);
}
/**
* Handles book outline form submissions.
*/
function book_outline_submit($form_id, $form_values) {
$op = $_POST['op'];
$node = node_load($form_values['nid']);
switch ($op) {
case t('Add to book outline'):
db_query('INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $form_values['parent'], $form_values['weight']);
db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid);
drupal_set_message(t('The post has been added to the book.'));
break;
case t('Update book outline'):
db_query('UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d', $form_values['parent'], $form_values['weight'], $node->vid);
db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid);
drupal_set_message(t('The book outline has been updated.'));
break;
case t('Remove from book outline'):
db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
drupal_set_message(t('The post has been removed from the book.'));
break;
Dries Buytaert
committed
return "node/$node->nid";
Dries Buytaert
committed
/**
* Given a node, this function returns an array of 'book node' objects
* representing the path in the book tree from the root to the
* parent of the given node.
* @param node - a book node object for which to compute the path
*
* @return - an array of book node objects representing the path of
* nodes root to parent of the given node. Returns an empty array if
* the node does not exist or is not part of a book hierarchy.
*
Dries Buytaert
committed
*/
$parent = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $node->parent));
if (isset($parent->title)) {
/**
* Accumulates the nodes up to the root of the book from the given node in the $nodes array.
*/
Dries Buytaert
committed
$last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d ORDER BY b.weight DESC, n.title DESC'), $node->nid));
$nodes[] = $last_direct_child;
$nodes = book_location_down($last_direct_child, $nodes);
}
return $nodes;
}
Dries Buytaert
committed
/**
* Fetches the node object of the previous page of the book.
Dries Buytaert
committed
*/
// If the parent is zero, we are at the start of a book so there is no previous.
$direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight < %d OR (b.weight = %d AND n.title < '%s')) ORDER BY b.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title));
return $path ? (count($path) > 0 ? array_pop($path) : NULL) : $direct_above;
$prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d AND n.status = 1 AND n.moderate = 0'), $node->parent));
Dries Buytaert
committed
/**
* Fetches the node object of the next page of the book.
Dries Buytaert
committed
*/
$child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight ASC, n.title ASC'), $node->nid));
// No direct child: get next for this level or any parent in this book.
Dries Buytaert
committed
$path = book_location($node); // Path to top-level node including this one.
Dries Buytaert
committed
$next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight > %d OR (b.weight = %d AND n.title > '%s')) ORDER BY b.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title));
* Returns the content of a given node. If $teaser if TRUE, returns
* the teaser rather than full content. Displays the most recently
* approved revision of a node (if any) unless we have to display this
* page in the context of the moderation queue.
*/
// Return the page body.
return node_prepare($node, $teaser);
Dries Buytaert
committed
/**
* Implementation of hook_view().
*
* If not displayed on the main page, we render the node as a page in the
* book with extra links to the previous and next pages.
*/
$node = node_prepare($node, $teaser);
Dries Buytaert
committed
/**
* Implementation of hook_nodeapi().
*
* Appends book navigation to all nodes in the book.
Dries Buytaert
committed
*/
function book_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view':
if (!$teaser) {
$book = db_fetch_array(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
Dries Buytaert
committed
if ($node->moderate && user_access('administer nodes')) {
drupal_set_message(t("The post has been submitted for moderation and won't be accessible until it has been approved."));
Dries Buytaert
committed
}
$path = book_location($node);
// Construct the breadcrumb:
$node->breadcrumb = array(); // Overwrite the trail with a book trail.
foreach ($path as $level) {
$node->breadcrumb[] = array('path' => 'node/'. $level->nid, 'title' => $level->title);
}
$node->breadcrumb[] = array('path' => 'node/'. $node->nid);
$node->body .= theme('book_navigation', $node);
if ($page) {
menu_set_location($node->breadcrumb);
}
Dries Buytaert
committed
case 'delete revision':
db_query('DELETE FROM {book} WHERE vid = %d', $node->vid);
break;
Steven Wittens
committed
case 'delete':
db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
break;
Dries Buytaert
committed
/**
* Prepares the links to children (TOC) and forward/backward
Dries Buytaert
committed
* navigation for a node presented as a book page.
*
* @ingroup themeable
Dries Buytaert
committed
*/
function theme_book_navigation($node) {
Dries Buytaert
committed
$output = '';
Dries Buytaert
committed
Dries Buytaert
committed
$tree = book_tree($node->nid);
drupal_add_link(array('rel' => 'prev', 'href' => url('node/'. $prev->nid)));
Dries Buytaert
committed
$links .= l(t('‹ ') . $prev->title, 'node/'. $prev->nid, array('class' => 'page-previous', 'title' => t('Go to previous page')));
Dries Buytaert
committed
if ($node->parent) {
drupal_add_link(array('rel' => 'up', 'href' => url('node/'. $node->parent)));
Dries Buytaert
committed
$links .= l(t('up'), 'node/'. $node->parent, array('class' => 'page-up', 'title' => t('Go to parent page')));
drupal_add_link(array('rel' => 'next', 'href' => url('node/'. $next->nid)));
Dries Buytaert
committed
$links .= l($next->title . t(' ›'), 'node/'. $next->nid, array('class' => 'page-next', 'title' => t('Go to next page')));
Dries Buytaert
committed
if (isset($tree) || isset($links)) {
$output = '<div class="book-navigation">';
if (isset($tree)) {
$output .= $tree;
}
if (isset($links)) {
$output .= '<div class="page-links">'. $links .'</div>';
}
$output .= '</div>';
}
return $output;
/**
* This is a helper function for book_toc().
*/
Steven Wittens
committed
function book_toc_recurse($nid, $indent, $toc, $children, $exclude) {
if ($children[$nid]) {
foreach ($children[$nid] as $foo => $node) {
Steven Wittens
committed
if (!$exclude || $exclude != $node->nid) {
$toc[$node->nid] = $indent .' '. $node->title;
$toc = book_toc_recurse($node->nid, $indent .'--', $toc, $children, $exclude);
}
/**
* Returns an array of titles and nid entries of book pages in table of contents order.
*/
Steven Wittens
committed
function book_toc($exclude = 0) {
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title'));
if (!$children[$node->parent]) {
$children[$node->parent] = array();
}
$children[$node->parent][] = $node;
$toc = array();
// If the user has permission to create new books, add the top-level book page to the menu;
Steven Wittens
committed
if (user_access('create new books')) {
Dries Buytaert
committed
$toc[0] = '<'. t('top-level') .'>';
$toc = book_toc_recurse(0, '', $toc, $children, $exclude);
/**
* This is a helper function for book_tree()
*/
function book_tree_recurse($nid, $depth, $children, $unfold = array()) {
if (isset($children[$nid])) {
if (in_array($node->nid, $unfold)) {
if ($tree = book_tree_recurse($node->nid, $depth - 1, $children, $unfold)) {
$output .= '<li class="expanded">';
$output .= '<ul class="menu">'. $tree .'</ul>';
$output .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid) .'</li>';
}
}
else {
if ($tree = book_tree_recurse($node->nid, 1, $children)) {
$output .= '<li class="collapsed">'. l($node->title, 'node/'. $node->nid) .'</li>';
$output .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid) .'</li>';
/**
* Returns an HTML nested list (wrapped in a menu-class div) representing the book nodes
* as a tree.
*/
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
$list = isset($children[$node->parent]) ? $children[$node->parent] : array();
return '<ul class="menu">'. $tree .'</ul>';
Dries Buytaert
committed
/**
* Menu callback; prints a listing of all books.
Dries Buytaert
committed
*/
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
$books = array();
while ($node = db_fetch_object($result)) {
$books[] = l($node->title, 'node/'. $node->nid);
}
return theme('item_list', $books);
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Menu callback; Generates various representation of a book page with
* all descendants and prints the requested representation to output.
*
* 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().
Dries Buytaert
committed
*
* @param type
* - a string encoding the type of output requested.
* The following types are currently supported in book module
* html: HTML (printer friendly output)
* Other types are supported in contributed modules.
Dries Buytaert
committed
* @param nid
* - an integer representing the node id (nid) of the node to export
*
Dries Buytaert
committed
*/
function book_export($type = 'html', $nid = 0) {
Steven Wittens
committed
$type = drupal_strtolower($type);
$node_result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $nid);
if (db_num_rows($node_result) > 0) {
$node = db_fetch_object($node_result);
}
$depth = count(book_location($node)) + 1;
$export_function = 'book_export_' . $type;
if (function_exists($export_function)) {
print call_user_func($export_function, $nid, $depth);
Dries Buytaert
committed
}
else {
drupal_set_message(t('Unknown export format.'));
Dries Buytaert
committed
drupal_not_found();
Dries Buytaert
committed
}
/**
* This function is called by book_export() to generate HTML for 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
* @param depth
* - an integer giving the depth in the book hierarchy of the node
* which is to be exported
*
* @return
* - string containing HTML representing the node and its children in
* the book hierarchy
*/
function book_export_html($nid, $depth) {
if (user_access('see printer-friendly version')) {
$node = node_load($nid);
for ($i = 1; $i < $depth; $i++) {
$content .= "<div class=\"section-$i\">\n";
}
$content .= book_recurse($nid, $depth, 'book_node_visitor_html_pre', 'book_node_visitor_html_post');
for ($i = 1; $i < $depth; $i++) {
$content .= "</div>\n";
}
return theme('book_export_html', check_plain($node->title), $content);
}
else {
drupal_access_denied();
}
}
/**
* How the book's HTML export should be themed
*
* @ingroup themeable
*/
function theme_book_export_html($title, $content) {
global $base_url;
$html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
$html .= '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">';
$html .= "<head>\n<title>". $title ."</title>\n";
$html .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
$html .= '<base href="'. $base_url .'/" />' . "\n";
$html .= "<style type=\"text/css\">\n@import url(misc/print.css);\n</style>\n";
$html .= "</head>\n<body>\n". $content . "\n</body>\n</html>\n";
return $html;
}
* Traverses the book tree. Applies the $visit_pre() callback to each
* node, is called recursively for each child of the node (in weight,
* title order). Finally appends the output of the $visit_post()
* callback to the output before returning the generated output.
*
* @param nid
* - the node id (nid) of the root node of the book hierarchy.
* @param depth
* - the depth of the given node in the book hierarchy.
* @param visit_pre
* - a function callback to be called upon visiting a node in the tree
* @param visit_post
* - a function callback to be called after visiting a node in the tree,
* but before recursively visiting children.
* @return
* - the output generated in visiting each node
*/
function book_recurse($nid = 0, $depth = 1, $visit_pre, $visit_post) {
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.nid = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $nid);
$node = node_load($page->nid);
if (function_exists($visit_pre)) {
$output .= call_user_func($visit_pre, $node, $depth, $nid);
Dries Buytaert
committed
else {
$output .= book_node_visitor_html_pre($node, $depth, $nid);
$children = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $node->nid);
while ($childpage = db_fetch_object($children)) {
$childnode = node_load($childpage->nid);
if ($childnode->nid != $node->nid) {
$output .= book_recurse($childnode->nid, $depth + 1, $visit_pre, $visit_post);
}
}
if (function_exists($visit_post)) {
Dries Buytaert
committed
$output .= call_user_func($visit_post, $node, $depth);
else {
# default
Dries Buytaert
committed
$output .= book_node_visitor_html_post($node, $depth);
* Generates printer-friendly HTML for a node. This function
* is a 'pre-node' visitor function for book_recurse().
*
* @param $node
* - the node to generate output for.
* @param $depth
* - the depth of the given node in the hierarchy. This
* is used only for generating output.
* @param $nid
* - the node id (nid) of the given node. This
* is used only for generating output.
* @return
* - the HTML generated for the given node.
*/
Dries Buytaert
committed
function book_node_visitor_html_pre($node, $depth, $nid) {
// Output the content:
if (node_hook($node, 'content')) {
$node = node_invoke($node, 'content');
}
// Allow modules to change $node->body before viewing.
node_invoke_nodeapi($node, 'print', $node->body, FALSE);
Dries Buytaert
committed
$output .= "<div id=\"node-". $node->nid ."\" class=\"section-$depth\">\n";
$output .= "<h1 class=\"book-heading\">". check_plain($node->title) ."</h1>\n";
if ($node->body) {
$output .= $node->body;
}
return $output;
}
/**
* Finishes up generation of printer-friendly HTML after visiting a
* node. This function is a 'post-node' visitor function for
* book_recurse().
*/
Dries Buytaert
committed
function book_node_visitor_html_post($node, $depth) {
return "</div>\n";
}
function _book_admin_table($nodes = array()) {
$form = array(
'#theme' => 'book_admin_table',
'#tree' => TRUE,
);
foreach ($nodes as $node) {
$form = array_merge($form, _book_admin_table_tree($node, 0));
}
return $form;
function _book_admin_table_tree($node, $depth) {
$form = array();
$form[] = array(
'nid' => array('#type' => 'value', '#value' => $node->nid),
'depth' => array('#type' => 'value', '#value' => $depth),
'title' => array(
'#type' => 'textfield',
'#default_value' => $node->title,
'#maxlength' => 255,
),
'weight' => array(
'#type' => 'weight',
'#default_value' => $node->weight,
'#delta' => 15,
),
$children = db_query(db_rewrite_sql('SELECT n.nid, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d ORDER BY b.weight, n.title'), $node->nid);
while ($child = db_fetch_object($children)) {
$form = array_merge($form, _book_admin_table_tree(node_load($child->nid), $depth + 1));
}
return $form;
function theme_book_admin_table($form) {
$header = array(t('Title'), t('Weight'), array('data' => t('Operations'), 'colspan' => '3'));
foreach (element_children($form) as $key) {
$nid = $form[$key]['nid']['#value'];
Dries Buytaert
committed
$pid = $form[0]['nid']['#value'];
$rows[] = array(
'<div style="padding-left: '. (25 * $form[$key]['depth']['#value']) .'px;">'. form_render($form[$key]['title']) .'</div>',
form_render($form[$key]['weight']),
l(t('view'), 'node/'. $nid),
l(t('edit'), 'node/'. $nid .'/edit'),
Dries Buytaert
committed
l(t('delete'), 'node/'. $nid .'/delete', NULL, 'destination=admin/node/book'. (arg(3) == 'orphan' ? '/orphan' : '') . ($pid != $nid ? '/'.$pid : ''))
return theme('table', $header, $rows);
Dries Buytaert
committed
/**
* Display an administrative view of the hierarchy of a book.
*/
function book_admin_edit($nid) {
$node = node_load($nid);
Dries Buytaert
committed
if ($node->nid) {
drupal_set_title(check_plain($node->title));
$form['table'] = _book_admin_table(array($node));
$form['save'] = array(
'#type' => 'submit',
'#value' => t('Save book pages'),
);
return drupal_get_form('book_admin_edit', $form);
Dries Buytaert
committed
else {
drupal_not_found();
}
* Menu callback; displays a listing of all orphaned book pages.
function book_admin_orphan() {
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid'));
$pages = array();
while ($page = db_fetch_object($result)) {
$pages[$page->nid] = $page;
}
$orphans = array();
if (count($pages)) {
foreach ($pages as $page) {
if ($page->parent && empty($pages[$page->parent])) {
$orphans[] = node_load($page->nid);
if (count($orphans)) {
$form = array();
$form['table'] = _book_admin_table($orphans);
$form['save'] = array(
'#type' => 'submit',
'#value' => t('Save book pages'),
);
return drupal_get_form('book_admin_edit', $form);
}
else {
return '<p>'. t('There are no orphan pages.') .'</p>';
Dries Buytaert
committed
function book_admin_edit_submit($form_id, $form_values) {
foreach ($form_values['table'] as $row) {
$node = node_load($row['nid']);
if ($row['title'] != $node->title || $row['weight'] != $node->weight) {
$node->title = $row['title'];
$node->weight = $row['weight'];
node_save($node);
watchdog('content', t('%type: updated %title.', array('%type' => theme('placeholder', t('book')), '%title' => theme('placeholder', $node->title))), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
if (is_numeric(arg(3))) {
// Updating pages in a single book.
$book = node_load(arg(3));
drupal_set_message(t('Updated book %title.', array('%title' => theme('placeholder', $book->title))));
}
else {
// Updating the orphan pages.
drupal_set_message(t('Updated orphan book pages.'));
}
Dries Buytaert
committed
/**
* Menu callback; displays the book administration page.
Dries Buytaert
committed
*/
Dries Buytaert
committed
if ($nid) {
return book_admin_edit($nid);
}
else {
return book_admin_overview();
}
}
/**
* Returns an administrative overview of all books.
*/
Dries Buytaert
committed
function book_admin_overview() {
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 ORDER BY b.weight, n.title'));
Dries Buytaert
committed
while ($book = db_fetch_object($result)) {
$rows[] = array(l($book->title, "node/$book->nid"), l(t('outline'), "admin/node/book/$book->nid"));
}
$headers = array(t('Book'), t('Operations'));
return theme('table', $headers, $rows);
Dries Buytaert
committed
/**
* Implementation of hook_help().
*/
$output = '<p>'. t('The <em>book</em> content type is suited for creating structured, multi-page hypertexts such as site resource guides, manuals, and Frequently Asked Questions (FAQs). It permits a document to have chapters, sections, subsections, etc. Authors with suitable permissions can add pages to a collaborative book, placing them into the existing document by adding them to a table of contents menu. ') .'</p>';
$output .= '<p>'. t('Books have additional <em>previous</em>, <em>up</em>, and <em>next</em> navigation elements at the bottom of each page for moving through the text. Additional navigation may be provided by enabling the <em>book navigation block</em> on the <a href="%admin-block">block administration page</a>.', array('%admin-block' => url('admin/block'))) .'</p>';
$output .= '<p>'. t('Users can select the <em>printer-friendly version</em> link visible at the bottom of a book page to generate a printer-friendly display of the page and all of its subsections. ') .'</p>';
$output .= '<p>'. t('Administrators can view a book outline, from which is it possible to change the titles of sections, and their <i>weight</i> (thus reordering sections). From this outline, it is also possible to edit and/or delete book pages. Many content types besides pages (for example, blog entries, stories, and polls) can be added to a collaborative book by choosing the <em>outline</em> tab when viewing the post.') .'</p>';
Dries Buytaert
committed
$output .= t('<p>You can</p>