2 // $Id: book.module 144 2007-03-28 07:52:20Z thierry $
6 * Allows users to collaboratively author a book.
10 * Implementation of hook_node_info().
12 function book_node_info() {
13 return array('book' => array('name' => t('book page'), 'base' => 'book'));
17 * Implementation of hook_perm().
19 function book_perm() {
20 return array('outline posts in books', 'create book pages', 'create new books', 'edit book pages', 'edit own book pages', 'see printer-friendly version');
24 * Implementation of hook_access().
26 function book_access($op, $node) {
29 if ($op == 'create') {
30 // Only registered users can create book pages. Given the nature
31 // of the book module this is considered to be a good/safe idea.
32 return user_access('create book pages');
35 if ($op == 'update') {
36 // Only registered users can update book pages. Given the nature
37 // of the book module this is considered to be a good/safe idea.
38 // One can only update a book page if there are no suggested updates
39 // of that page waiting for approval. That is, only updates that
40 // don't overwrite the current or pending information are allowed.
42 if ((user_access('edit book pages') && !$node->moderate) || ($node->uid == $user->uid && user_access('edit own book pages'))) {
46 // do nothing. node-access() will determine further access
52 * Implementation of hook_link().
54 function book_link($type, $node = 0, $main = 0) {
58 if ($type == 'node' && isset($node->parent)) {
60 if (book_access('create', $node)) {
61 $links[] = l(t('add child page'), "node/add/book/parent/$node->nid");
63 if (user_access('see printer-friendly version')) {
64 $links[] = l(t('printer-friendly version'),
65 'book/export/html/'. $node->nid,
66 array('title' => t('Show a printer-friendly version of this book page and its sub-pages.')));
75 * Implementation of hook_menu().
77 function book_menu($may_cache) {
82 'path' => 'node/add/book',
83 'title' => t('book page'),
84 'access' => user_access('create book pages'));
86 'path' => 'admin/node/book',
87 'title' => t('books'),
88 'callback' => 'book_admin',
89 'access' => user_access('administer nodes'),
90 'type' => MENU_LOCAL_TASK,
93 'path' => 'admin/node/book/list',
95 'type' => MENU_DEFAULT_LOCAL_TASK);
97 'path' => 'admin/node/book/orphan',
98 'title' => t('orphan pages'),
99 'callback' => 'book_admin_orphan',
100 'type' => MENU_LOCAL_TASK,
104 'title' => t('books'),
105 'callback' => 'book_render',
106 'access' => user_access('access content'),
107 'type' => MENU_SUGGESTED_ITEM);
109 'path' => 'book/export',
110 'callback' => 'book_export',
111 'access' => user_access('access content'),
112 'type' => MENU_CALLBACK);
115 // To avoid SQL overhead, check whether we are on a node page and whether the
116 // user is allowed to outline posts in books.
117 if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('outline posts in books')) {
118 // Only add the outline-tab for non-book pages:
119 $result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.nid = %d AND n.type != 'book'"), arg(1));
120 if (db_num_rows($result) > 0) {
122 'path' => 'node/'. arg(1) .'/outline',
123 'title' => t('outline'),
124 'callback' => 'book_outline',
125 'callback arguments' => array(arg(1)),
126 'access' => user_access('outline posts in books'),
127 'type' => MENU_LOCAL_TASK,
137 * Implementation of hook_block().
139 * Displays the book table of contents in a block when the current page is a
140 * single-node view of a book node.
142 function book_block($op = 'list', $delta = 0) {
145 $block[0]['info'] = t('Book navigation');
148 else if ($op == 'view') {
149 // Only display this block when the user is browsing a book:
150 if (arg(0) == 'node' && is_numeric(arg(1))) {
151 $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));
152 if (db_num_rows($result) > 0) {
153 $node = db_fetch_object($result);
155 $path = book_location($node);
159 foreach ($path as $key => $node) {
160 $expand[] = $node->nid;
163 $block['subject'] = check_plain($path[0]->title);
164 $block['content'] = book_tree($expand[0], 5, $expand);
173 * Implementation of hook_load().
175 function book_load($node) {
176 $book = db_fetch_object(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
181 * Implementation of hook_insert().
183 function book_insert($node) {
184 db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
188 * Implementation of hook_update().
190 function book_update($node) {
191 if ($node->revision) {
192 db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
195 db_query("UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d", $node->parent, $node->weight, $node->vid);
200 * Implementation of hook_delete().
202 function book_delete(&$node) {
203 db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
207 * Implementation of hook_submit().
209 function book_submit(&$node) {
211 // Set default values for non-administrators.
212 if (!user_access('administer nodes')) {
215 $book->uid = $user->uid;
216 $book->name = $user->uid ? $user->name : '';
221 * Implementation of hook_form().
223 function book_form(&$node) {
224 if ($node->nid && !$node->parent && !user_access('create new books')) {
225 $form['parent'] = array('#type' => 'value', '#value' => $node->parent);
228 $form['parent'] = array('#type' => 'select',
229 '#title' => t('Parent'),
230 '#default_value' => ($node->parent ? $node->parent : arg(4)),
231 '#options' => book_toc($node->nid),
233 '#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.'),
237 $form['title'] = array('#type' => 'textfield',
238 '#title' => t('Title'),
240 '#default_value' => $node->title,
243 $form['body_filter']['body'] = array('#type' => 'textarea',
244 '#title' => t('Body'),
245 '#default_value' => $node->body,
249 $form['body_filter']['format'] = filter_form($node->format);
251 $form['log'] = array(
252 '#type' => 'textarea',
253 '#title' => t('Log message'),
254 '#default_value' => $node->log,
256 '#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.'),
259 if (user_access('administer nodes')) {
260 $form['weight'] = array('#type' => 'weight',
261 '#title' => t('Weight'),
262 '#default_value' => $node->weight,
265 '#description' => t('Pages at a given level are ordered first by weight and then by title.'),
269 // If a regular user updates a book page, we create a new revision
270 // authored by that user:
271 $form['revision'] = array('#type' => 'hidden', '#value' => 1);
278 * Implementation of function book_outline()
279 * Handles all book outline operations.
281 function book_outline($nid) {
282 $node = node_load($nid);
283 $page = book_load($node);
285 $form['parent'] = array('#type' => 'select',
286 '#title' => t('Parent'),
287 '#default_value' => $page->parent,
288 '#options' => book_toc($node->nid),
289 '#description' => t('The parent page in the book.'),
291 $form['weight'] = array('#type' => 'weight',
292 '#title' => t('Weight'),
293 '#default_value' => $page->weight,
295 '#description' => t('Pages at a given level are ordered first by weight and then by title.'),
297 $form['log'] = array('#type' => 'textarea',
298 '#title' => t('Log message'),
299 '#default_value' => $node->log,
300 '#description' => t('An explanation to help other authors understand your motivations to put this post into the book.'),
303 $form['nid'] = array('#type' => 'value', '#value' => $nid);
305 $form['update'] = array('#type' => 'submit',
306 '#value' => t('Update book outline'),
308 $form['remove'] = array('#type' => 'submit',
309 '#value' => t('Remove from book outline'),
313 $form['add'] = array('#type' => 'submit', '#value' => t('Add to book outline'));
316 drupal_set_title(check_plain($node->title));
317 return drupal_get_form('book_outline', $form);
321 * Handles book outline form submissions.
323 function book_outline_submit($form_id, $form_values) {
325 $node = node_load($form_values['nid']);
328 case t('Add to book outline'):
329 db_query('INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $form_values['parent'], $form_values['weight']);
330 db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid);
331 drupal_set_message(t('The post has been added to the book.'));
333 case t('Update book outline'):
334 db_query('UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d', $form_values['parent'], $form_values['weight'], $node->vid);
335 db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid);
336 drupal_set_message(t('The book outline has been updated.'));
338 case t('Remove from book outline'):
339 db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
340 drupal_set_message(t('The post has been removed from the book.'));
343 return "node/$node->nid";
347 * Given a node, this function returns an array of 'book node' objects
348 * representing the path in the book tree from the root to the
349 * parent of the given node.
351 * @param node - a book node object for which to compute the path
353 * @return - an array of book node objects representing the path of
354 * nodes root to parent of the given node. Returns an empty array if
355 * the node does not exist or is not part of a book hierarchy.
358 function book_location($node, $nodes = array()) {
359 $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));
360 if (isset($parent->title)) {
361 $nodes = book_location($parent, $nodes);
368 * Accumulates the nodes up to the root of the book from the given node in the $nodes array.
370 function book_location_down($node, $nodes = array()) {
371 $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));
372 if ($last_direct_child) {
373 $nodes[] = $last_direct_child;
374 $nodes = book_location_down($last_direct_child, $nodes);
380 * Fetches the node object of the previous page of the book.
382 function book_prev($node) {
383 // If the parent is zero, we are at the start of a book so there is no previous.
384 if ($node->parent == 0) {
388 // Previous on the same level:
389 $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));
391 // Get last leaf of $above.
392 $path = book_location_down($direct_above);
394 return $path ? (count($path) > 0 ? array_pop($path) : NULL) : $direct_above;
398 $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));
404 * Fetches the node object of the next page of the book.
406 function book_next($node) {
407 // get first direct child
408 $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));
413 // No direct child: get next for this level or any parent in this book.
414 $path = book_location($node); // Path to top-level node including this one.
417 while (($leaf = array_pop($path)) && count($path)) {
418 $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));
426 * Returns the content of a given node. If $teaser if true, returns
427 * the teaser rather than full content. Displays the most recently
428 * approved revision of a node (if any) unless we have to display this
429 * page in the context of the moderation queue.
431 function book_content($node, $teaser = FALSE) {
432 // Return the page body.
433 return node_prepare($node, $teaser);
437 * Implementation of hook_view().
439 * If not displayed on the main page, we render the node as a page in the
440 * book with extra links to the previous and next pages.
442 function book_view(&$node, $teaser = FALSE, $page = FALSE) {
443 $node = node_prepare($node, $teaser);
447 * Implementation of hook_nodeapi().
449 * Appends book navigation to all nodes in the book.
451 function book_nodeapi(&$node, $op, $teaser, $page) {
455 $book = db_fetch_array(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
457 if ($node->moderate && user_access('administer nodes')) {
458 drupal_set_message(t("The post has been submitted for moderation and won't be accessible until it has been approved."));
461 foreach ($book as $key => $value) {
462 $node->$key = $value;
465 $path = book_location($node);
466 // Construct the breadcrumb:
467 $node->breadcrumb = array(); // Overwrite the trail with a book trail.
468 foreach ($path as $level) {
469 $node->breadcrumb[] = array('path' => 'node/'. $level->nid, 'title' => $level->title);
471 $node->breadcrumb[] = array('path' => 'node/'. $node->nid);
473 $node->body .= theme('book_navigation', $node);
476 menu_set_location($node->breadcrumb);
481 case 'delete revision':
482 db_query('DELETE FROM {book} WHERE vid = %d', $node->vid);
485 db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
491 * Prepares the links to children (TOC) and forward/backward
492 * navigation for a node presented as a book page.
496 function theme_book_navigation($node) {
501 $tree = book_tree($node->nid);
503 if ($prev = book_prev($node)) {
504 drupal_add_link(array('rel' => 'prev', 'href' => url('node/'. $prev->nid)));
505 $links .= l(t('‹ ') . $prev->title, 'node/'. $prev->nid, array('class' => 'page-previous', 'title' => t('Go to previous page')));
508 drupal_add_link(array('rel' => 'up', 'href' => url('node/'. $node->parent)));
509 $links .= l(t('up'), 'node/'. $node->parent, array('class' => 'page-up', 'title' => t('Go to parent page')));
511 if ($next = book_next($node)) {
512 drupal_add_link(array('rel' => 'next', 'href' => url('node/'. $next->nid)));
513 $links .= l($next->title . t(' ›'), 'node/'. $next->nid, array('class' => 'page-next', 'title' => t('Go to next page')));
516 if (isset($tree) || isset($links)) {
517 $output = '<div class="book-navigation">';
522 $output .= '<div class="page-links">'. $links .'</div>';
532 * This is a helper function for book_toc().
534 function book_toc_recurse($nid, $indent, $toc, $children, $exclude) {
535 if ($children[$nid]) {
536 foreach ($children[$nid] as $foo => $node) {
537 if (!$exclude || $exclude != $node->nid) {
538 $toc[$node->nid] = $indent .' '. $node->title;
539 $toc = book_toc_recurse($node->nid, $indent .'--', $toc, $children, $exclude);
548 * Returns an array of titles and nid entries of book pages in table of contents order.
550 function book_toc($exclude = 0) {
551 $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'));
553 while ($node = db_fetch_object($result)) {
554 if (!$children[$node->parent]) {
555 $children[$node->parent] = array();
557 $children[$node->parent][] = $node;
561 // If the user has permission to create new books, add the top-level book page to the menu;
562 if (user_access('create new books')) {
563 $toc[0] = '<'. t('top-level') .'>';
566 $toc = book_toc_recurse(0, '', $toc, $children, $exclude);
572 * This is a helper function for book_tree()
574 function book_tree_recurse($nid, $depth, $children, $unfold = array()) {
577 if (isset($children[$nid])) {
578 foreach ($children[$nid] as $foo => $node) {
579 if (in_array($node->nid, $unfold)) {
580 if ($tree = book_tree_recurse($node->nid, $depth - 1, $children, $unfold)) {
581 $output .= '<li class="expanded">';
582 $output .= l($node->title, 'node/'. $node->nid);
583 $output .= '<ul class="menu">'. $tree .'</ul>';
587 $output .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid) .'</li>';
591 if ($tree = book_tree_recurse($node->nid, 1, $children)) {
592 $output .= '<li class="collapsed">'. l($node->title, 'node/'. $node->nid) .'</li>';
595 $output .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid) .'</li>';
606 * Returns an HTML nested list (wrapped in a menu-class div) representing the book nodes
609 function book_tree($parent = 0, $depth = 3, $unfold = array()) {
610 $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'));
612 while ($node = db_fetch_object($result)) {
613 $list = isset($children[$node->parent]) ? $children[$node->parent] : array();
615 $children[$node->parent] = $list;
618 if ($tree = book_tree_recurse($parent, $depth, $children, $unfold)) {
619 return '<ul class="menu">'. $tree .'</ul>';
624 * Menu callback; prints a listing of all books.
626 function book_render() {
627 $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'));
630 while ($node = db_fetch_object($result)) {
631 $books[] = l($node->title, 'node/'. $node->nid);
634 return theme('item_list', $books);
638 * Menu callback; Generates various representation of a book page with
639 * all descendants and prints the requested representation to output.
641 * The function delegates the generation of output to helper functions.
642 * The function name is derived by prepending 'book_export_' to the
643 * given output type. So, e.g., a type of 'html' results in a call to
644 * the function book_export_html().
647 * - a string encoding the type of output requested.
648 * The following types are currently supported in book module
649 * html: HTML (printer friendly output)
650 * Other types are supported in contributed modules.
652 * - an integer representing the node id (nid) of the node to export
655 function book_export($type = 'html', $nid = 0) {
656 $type = drupal_strtolower($type);
657 $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);
658 if (db_num_rows($node_result) > 0) {
659 $node = db_fetch_object($node_result);
661 $depth = count(book_location($node)) + 1;
662 $export_function = 'book_export_' . $type;
664 if (function_exists($export_function)) {
665 print call_user_func($export_function, $nid, $depth);
668 drupal_set_message(t('Unknown export format.'));
674 * This function is called by book_export() to generate HTML for export.
676 * The given node is /embedded to its absolute depth in a top level
677 * section/. For example, a child node with depth 2 in the hierarchy
678 * is contained in (otherwise empty) <div> elements
679 * corresponding to depth 0 and depth 1. This is intended to support
680 * WYSIWYG output - e.g., level 3 sections always look like level 3
681 * sections, no matter their depth relative to the node selected to be
682 * exported as printer-friendly HTML.
685 * - an integer representing the node id (nid) of the node to export
687 * - an integer giving the depth in the book hierarchy of the node
688 * which is to be exported
691 * - string containing HTML representing the node and its children in
694 function book_export_html($nid, $depth) {
695 if (user_access('see printer-friendly version')) {
696 $node = node_load($nid);
697 for ($i = 1; $i < $depth; $i++) {
698 $content .= "<div class=\"section-$i\">\n";
700 $content .= book_recurse($nid, $depth, 'book_node_visitor_html_pre', 'book_node_visitor_html_post');
701 for ($i = 1; $i < $depth; $i++) {
702 $content .= "</div>\n";
704 return theme('book_export_html', check_plain($node->title), $content);
707 drupal_access_denied();
712 * How the book's HTML export should be themed
716 function theme_book_export_html($title, $content) {
718 $html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
719 $html .= '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">';
720 $html .= "<head>\n<title>". $title ."</title>\n";
721 $html .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
722 $html .= '<base href="'. $base_url .'/" />' . "\n";
723 $html .= "<style type=\"text/css\">\n@import url(misc/print.css);\n</style>\n";
724 $html .= "</head>\n<body>\n". $content . "\n</body>\n</html>\n";
729 * Traverses the book tree. Applies the $visit_pre() callback to each
730 * node, is called recursively for each child of the node (in weight,
731 * title order). Finally appends the output of the $visit_post()
732 * callback to the output before returning the generated output.
735 * - the node id (nid) of the root node of the book hierarchy.
737 * - the depth of the given node in the book hierarchy.
739 * - a function callback to be called upon visiting a node in the tree
741 * - a function callback to be called after visiting a node in the tree,
742 * but before recursively visiting children.
744 * - the output generated in visiting each node
746 function book_recurse($nid = 0, $depth = 1, $visit_pre, $visit_post) {
747 $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);
748 while ($page = db_fetch_object($result)) {
750 $node = node_load($page->nid);
753 if (function_exists($visit_pre)) {
754 $output .= call_user_func($visit_pre, $node, $depth, $nid);
757 $output .= book_node_visitor_html_pre($node, $depth, $nid);
760 $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);
761 while ($childpage = db_fetch_object($children)) {
762 $childnode = node_load($childpage->nid);
763 if ($childnode->nid != $node->nid) {
764 $output .= book_recurse($childnode->nid, $depth + 1, $visit_pre, $visit_post);
767 if (function_exists($visit_post)) {
768 $output .= call_user_func($visit_post, $node, $depth);
772 $output .= book_node_visitor_html_post($node, $depth);
781 * Generates printer-friendly HTML for a node. This function
782 * is a 'pre-node' visitor function for book_recurse().
785 * - the node to generate output for.
787 * - the depth of the given node in the hierarchy. This
788 * is used only for generating output.
790 * - the node id (nid) of the given node. This
791 * is used only for generating output.
793 * - the HTML generated for the given node.
795 function book_node_visitor_html_pre($node, $depth, $nid) {
796 // Output the content:
797 if (node_hook($node, 'content')) {
798 $node = node_invoke($node, 'content');
800 // Allow modules to change $node->body before viewing.
801 node_invoke_nodeapi($node, 'print', $node->body, false);
803 $output .= "<div id=\"node-". $node->nid ."\" class=\"section-$depth\">\n";
804 $output .= "<h1 class=\"book-heading\">". check_plain($node->title) ."</h1>\n";
807 $output .= $node->body;
813 * Finishes up generation of printer-friendly HTML after visiting a
814 * node. This function is a 'post-node' visitor function for
817 function book_node_visitor_html_post($node, $depth) {
821 function _book_admin_table($nodes = array()) {
823 '#theme' => 'book_admin_table',
827 foreach ($nodes as $node) {
828 $form = array_merge($form, _book_admin_table_tree($node, 0));
834 function _book_admin_table_tree($node, $depth) {
838 'nid' => array('#type' => 'value', '#value' => $node->nid),
839 'depth' => array('#type' => 'value', '#value' => $depth),
841 '#type' => 'textfield',
842 '#default_value' => $node->title,
847 '#default_value' => $node->weight,
852 $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);
853 while ($child = db_fetch_object($children)) {
854 $form = array_merge($form, _book_admin_table_tree(node_load($child->nid), $depth + 1));
860 function theme_book_admin_table($form) {
861 $header = array(t('Title'), t('Weight'), array('data' => t('Operations'), 'colspan' => '3'));
864 foreach (element_children($form) as $key) {
865 $nid = $form[$key]['nid']['#value'];
866 $pid = $form[0]['nid']['#value'];
868 // Don't return to the parent book page if it is deleted.
872 '<div style="padding-left: '. (25 * $form[$key]['depth']['#value']) .'px;">'. form_render($form[$key]['title']) .'</div>',
873 form_render($form[$key]['weight']),
874 l(t('view'), 'node/'. $nid),
875 l(t('edit'), 'node/'. $nid .'/edit'),
876 l(t('delete'), 'node/'. $nid .'/delete', NULL, 'destination=admin/node/book/'. (arg(3) == 'orphan' ? 'orphan' : $pid)),
880 return theme('table', $header, $rows);
884 * Display an administrative view of the hierarchy of a book.
886 function book_admin_edit($nid) {
887 $node = node_load($nid);
889 drupal_set_title(check_plain($node->title));
892 $form['table'] = _book_admin_table(array($node));
893 $form['save'] = array(
895 '#value' => t('Save book pages'),
898 return drupal_get_form('book_admin_edit', $form);
906 * Menu callback; displays a listing of all orphaned book pages.
908 function book_admin_orphan() {
909 $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'));
912 while ($page = db_fetch_object($result)) {
913 $pages[$page->nid] = $page;
918 foreach ($pages as $page) {
919 if ($page->parent && empty($pages[$page->parent])) {
920 $orphans[] = node_load($page->nid);
925 if (count($orphans)) {
928 $form['table'] = _book_admin_table($orphans);
929 $form['save'] = array(
931 '#value' => t('Save book pages'),
934 return drupal_get_form('book_admin_edit', $form);
937 return '<p>'. t('There are no orphan pages.') .'</p>';
941 function book_admin_edit_submit($form_id, $form_values) {
942 foreach ($form_values['table'] as $row) {
943 $node = node_load($row['nid']);
945 if ($row['title'] != $node->title || $row['weight'] != $node->weight) {
946 $node->title = $row['title'];
947 $node->weight = $row['weight'];
950 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));
954 if (is_numeric(arg(3))) {
955 // Updating pages in a single book.
956 $book = node_load(arg(3));
957 drupal_set_message(t('Updated book %title.', array('%title' => theme('placeholder', $book->title))));
960 // Updating the orphan pages.
961 drupal_set_message(t('Updated orphan book pages.'));
966 * Menu callback; displays the book administration page.
968 function book_admin($nid = 0) {
970 return book_admin_edit($nid);
973 return book_admin_overview();
978 * Returns an administrative overview of all books.
980 function book_admin_overview() {
981 $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'));
982 while ($book = db_fetch_object($result)) {
983 $rows[] = array(l($book->title, "node/$book->nid"), l(t('outline'), "admin/node/book/$book->nid"));
985 $headers = array(t('Book'), t('Operations'));
987 return theme('table', $headers, $rows);
991 * Implementation of hook_help().
993 function book_help($section) {
995 case 'admin/help#book':
996 $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>';
997 $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>';
998 $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>';
999 $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>';
1000 $output .= t('<p>You can</p>
1002 <li>create new book pages: <a href="%node-add-book">create content >> book page</a>.</li>
1003 <li>administer individual books (choose a book from list): <a href="%admin-node-book">administer >> content >> books</a>.</li>
1004 <li>set workflow and other global book settings on the book configuration page: <a href="%admin-settings-content-types-book-page" title="book page content type">administer >> settings >> content types >> configure book page</a>.</li>
1005 <li>enable the book navigation block: <a href="%admin-block">administer >> blocks</a>.</li>
1006 <li>control who can create, edit, and outline posts in books by setting access permissions: <a href="%admin-access">administer >> access control</a>.</li>
1008 ', array('%node-add-book' => url('node/add/book'), '%admin-node-book' => url('admin/node/book'), '%admin-settings-content-types-book-page' => url('admin/settings/content-types/book'), '%admin-block' => url('admin/block'), '%admin-access' => url('admin/access')));
1009 $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%book">Book page</a>.', array('%book' => 'http://drupal.org/handbook/modules/book/')) .'</p>';
1011 case 'admin/modules#description':
1012 return t('Allows users to collaboratively author a book.');
1013 case 'admin/node/book':
1014 return t('<p>The book module offers a means to organize content, authored by many users, in an online manual, outline or FAQ.</p>');
1015 case 'admin/node/book/orphan':
1016 return t('<p>Pages in a book are like a tree. As pages are edited, reorganized and removed, child pages might be left with no link to the rest of the book. Such pages are referred to as "orphan pages". On this page, administrators can review their books for orphans and reattach those pages as desired.</p>');
1017 case 'node/add#book':
1018 return t("A book is a collaborative writing effort: users can collaborate writing the pages of the book, positioning the pages in the right order, and reviewing or modifying pages previously written. So when you have some information to share or when you read a page of the book and you didn't like it, or if you think a certain page could have been written better, you can do something about it.");
1021 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'outline') {
1022 return t('The outline feature allows you to include posts in the <a href="%book">book hierarchy</a>.', array('%book' => url('book')));