| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @file 5 * Allows users to structure the pages of a site in a hierarchy or outline. 6 */ 7 8 /** 9 * Implementation of hook_theme() 10 */ 11 function book_theme() { 12 return array( 13 'book_navigation' => array( 14 'arguments' => array('book_link' => NULL), 15 'template' => 'book-navigation', 16 ), 17 'book_export_html' => array( 18 'arguments' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL), 19 'template' => 'book-export-html', 20 ), 21 'book_admin_table' => array( 22 'arguments' => array('form' => NULL), 23 ), 24 'book_title_link' => array( 25 'arguments' => array('link' => NULL), 26 ), 27 'book_all_books_block' => array( 28 'arguments' => array('book_menus' => array()), 29 'template' => 'book-all-books-block', 30 ), 31 'book_node_export_html' => array( 32 'arguments' => array('node' => NULL, 'children' => NULL), 33 'template' => 'book-node-export-html', 34 ), 35 ); 36 } 37 38 /** 39 * Implementation of hook_perm(). 40 */ 41 function book_perm() { 42 return array('add content to books', 'administer book outlines', 'create new books', 'access printer-friendly version'); 43 } 44 45 /** 46 * Implementation of hook_link(). 47 */ 48 function book_link($type, $node = NULL, $teaser = FALSE) { 49 $links = array(); 50 51 if ($type == 'node' && isset($node->book)) { 52 if (!$teaser) { 53 $child_type = variable_get('book_child_type', 'book'); 54 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) { 55 $links['book_add_child'] = array( 56 'title' => t('Add child page'), 57 'href' => "node/add/". str_replace('_', '-', $child_type), 58 'query' => "parent=". $node->book['mlid'], 59 ); 60 } 61 if (user_access('access printer-friendly version')) { 62 $links['book_printer'] = array( 63 'title' => t('Printer-friendly version'), 64 'href' => 'book/export/html/'. $node->nid, 65 'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.')) 66 ); 67 } 68 } 69 } 70 return $links; 71 } 72 73 /** 74 * Implementation of hook_menu(). 75 */ 76 function book_menu() { 77 $items['admin/content/book'] = array( 78 'title' => 'Books', 79 'description' => "Manage your site's book outlines.", 80 'page callback' => 'book_admin_overview', 81 'access arguments' => array('administer book outlines'), 82 'file' => 'book.admin.inc', 83 ); 84 $items['admin/content/book/list'] = array( 85 'title' => 'List', 86 'type' => MENU_DEFAULT_LOCAL_TASK, 87 ); 88 $items['admin/content/book/settings'] = array( 89 'title' => 'Settings', 90 'page callback' => 'drupal_get_form', 91 'page arguments' => array('book_admin_settings'), 92 'access arguments' => array('administer site configuration'), 93 'type' => MENU_LOCAL_TASK, 94 'weight' => 8, 95 'file' => 'book.admin.inc', 96 ); 97 $items['admin/content/book/%node'] = array( 98 'title' => 'Re-order book pages and change titles', 99 'page callback' => 'drupal_get_form', 100 'page arguments' => array('book_admin_edit', 3), 101 'access callback' => '_book_outline_access', 102 'access arguments' => array(3), 103 'type' => MENU_CALLBACK, 104 'file' => 'book.admin.inc', 105 ); 106 $items['book'] = array( 107 'title' => 'Books', 108 'page callback' => 'book_render', 109 'access arguments' => array('access content'), 110 'type' => MENU_SUGGESTED_ITEM, 111 'file' => 'book.pages.inc', 112 ); 113 $items['book/export/%/%'] = array( 114 'page callback' => 'book_export', 115 'page arguments' => array(2, 3), 116 'access arguments' => array('access printer-friendly version'), 117 'type' => MENU_CALLBACK, 118 'file' => 'book.pages.inc', 119 ); 120 $items['node/%node/outline'] = array( 121 'title' => 'Outline', 122 'page callback' => 'book_outline', 123 'page arguments' => array(1), 124 'access callback' => '_book_outline_access', 125 'access arguments' => array(1), 126 'type' => MENU_LOCAL_TASK, 127 'weight' => 2, 128 'file' => 'book.pages.inc', 129 ); 130 $items['node/%node/outline/remove'] = array( 131 'title' => 'Remove from outline', 132 'page callback' => 'drupal_get_form', 133 'page arguments' => array('book_remove_form', 1), 134 'access callback' => '_book_outline_remove_access', 135 'access arguments' => array(1), 136 'type' => MENU_CALLBACK, 137 'file' => 'book.pages.inc', 138 ); 139 $items['book/js/form'] = array( 140 'page callback' => 'book_form_update', 141 'access arguments' => array('access content'), 142 'type' => MENU_CALLBACK, 143 'file' => 'book.pages.inc', 144 ); 145 return $items; 146 } 147 148 /** 149 * Menu item access callback - determine if the outline tab is accessible. 150 */ 151 function _book_outline_access($node) { 152 return user_access('administer book outlines') && node_access('view', $node); 153 } 154 155 /** 156 * Menu item access callback - determine if the user can remove nodes from the outline. 157 */ 158 function _book_outline_remove_access($node) { 159 return isset($node->book) && ($node->book['bid'] != $node->nid) && _book_outline_access($node); 160 } 161 162 /** 163 * Implementation of hook_init(). Add's the book module's CSS. 164 */ 165 function book_init() { 166 drupal_add_css(drupal_get_path('module', 'book') .'/book.css'); 167 } 168 169 /** 170 * Implementation of hook_block(). 171 * 172 * Displays the book table of contents in a block when the current page is a 173 * single-node view of a book node. 174 */ 175 function book_block($op = 'list', $delta = 0, $edit = array()) { 176 $block = array(); 177 switch ($op) { 178 case 'list': 179 $block[0]['info'] = t('Book navigation'); 180 $block[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE; 181 return $block; 182 case 'view': 183 $current_bid = 0; 184 if ($node = menu_get_object()) { 185 $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid']; 186 } 187 if (variable_get('book_block_mode', 'all pages') == 'all pages') { 188 $block['subject'] = t('Book navigation'); 189 $book_menus = array(); 190 $pseudo_tree = array(0 => array('below' => FALSE)); 191 foreach (book_get_books() as $book_id => $book) { 192 if ($book['bid'] == $current_bid) { 193 // If the current page is a node associated with a book, the menu 194 // needs to be retrieved. 195 $book_menus[$book_id] = menu_tree_output(menu_tree_all_data($node->book['menu_name'], $node->book)); 196 } 197 else { 198 // Since we know we will only display a link to the top node, there 199 // is no reason to run an additional menu tree query for each book. 200 $book['in_active_trail'] = FALSE; 201 $pseudo_tree[0]['link'] = $book; 202 $book_menus[$book_id] = menu_tree_output($pseudo_tree); 203 } 204 } 205 $block['content'] = theme('book_all_books_block', $book_menus); 206 } 207 elseif ($current_bid) { 208 // Only display this block when the user is browsing a book. 209 $title = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $node->book['bid'])); 210 // Only show the block if the user has view access for the top-level node. 211 if ($title) { 212 $tree = menu_tree_all_data($node->book['menu_name'], $node->book); 213 // There should only be one element at the top level. 214 $data = array_shift($tree); 215 $block['subject'] = theme('book_title_link', $data['link']); 216 $block['content'] = ($data['below']) ? menu_tree_output($data['below']) : ''; 217 } 218 } 219 return $block; 220 case 'configure': 221 $options = array( 222 'all pages' => t('Show block on all pages'), 223 'book pages' => t('Show block only on book pages'), 224 ); 225 $form['book_block_mode'] = array( 226 '#type' => 'radios', 227 '#title' => t('Book navigation block display'), 228 '#options' => $options, 229 '#default_value' => variable_get('book_block_mode', 'all pages'), 230 '#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."), 231 ); 232 return $form; 233 case 'save': 234 variable_set('book_block_mode', $edit['book_block_mode']); 235 break; 236 } 237 } 238 239 /** 240 * Generate the HTML output for a link to a book title when used as a block title. 241 * 242 * @ingroup themeable 243 */ 244 function theme_book_title_link($link) { 245 $link['options']['attributes']['class'] = 'book-title'; 246 return l($link['title'], $link['href'], $link['options']); 247 } 248 249 /** 250 * Returns an array of all books. 251 * 252 * This list may be used for generating a list of all the books, or for building 253 * the options for a form select. 254 */ 255 function book_get_books() { 256 static $all_books; 257 258 if (!isset($all_books)) { 259 $all_books = array(); 260 $result = db_query("SELECT DISTINCT(bid) FROM {book}"); 261 $nids = array(); 262 while ($book = db_fetch_array($result)) { 263 $nids[] = $book['bid']; 264 } 265 if ($nids) { 266 $result2 = db_query(db_rewrite_sql("SELECT n.type, n.title, b.*, ml.* FROM {book} b INNER JOIN {node} n on b.nid = n.nid INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE n.nid IN (". implode(',', $nids) .") AND n.status = 1 ORDER BY ml.weight, ml.link_title")); 267 while ($link = db_fetch_array($result2)) { 268 $link['href'] = $link['link_path']; 269 $link['options'] = unserialize($link['options']); 270 $all_books[$link['bid']] = $link; 271 } 272 } 273 } 274 return $all_books; 275 } 276 277 /** 278 * Implementation of hook_form_alter(). Adds the book fieldset to the node form. 279 * 280 * @see book_pick_book_submit() 281 * @see book_submit() 282 */ 283 function book_form_alter(&$form, $form_state, $form_id) { 284 285 if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) { 286 // Add elements to the node form 287 $node = $form['#node']; 288 289 $access = user_access('administer book outlines'); 290 if (!$access) { 291 if (user_access('add content to books') && ((!empty($node->book['mlid']) && !empty($node->nid)) || book_type_is_allowed($node->type))) { 292 // Already in the book hierarchy or this node type is allowed 293 $access = TRUE; 294 } 295 } 296 297 if ($access) { 298 _book_add_form_elements($form, $node); 299 $form['book']['pick-book'] = array( 300 '#type' => 'submit', 301 '#value' => t('Change book (update list of parents)'), 302 // Submit the node form so the parent select options get updated. 303 // This is typically only used when JS is disabled. Since the parent options 304 // won't be changed via AJAX, a button is provided in the node form to submit 305 // the form and generate options in the parent select corresponding to the 306 // selected book. This is similar to what happens during a node preview. 307 '#submit' => array('node_form_submit_build_node'), 308 '#weight' => 20, 309 ); 310 } 311 } 312 } 313 314 /** 315 * Build the parent selection form element for the node form or outline tab 316 * 317 * This function is also called when generating a new set of options during the 318 * AJAX callback, so an array is returned that can be used to replace an existing 319 * form element. 320 */ 321 function _book_parent_select($book_link) { 322 if (variable_get('menu_override_parent_selector', FALSE)) { 323 return array(); 324 } 325 // Offer a message or a drop-down to choose a different parent page. 326 $form = array( 327 '#type' => 'hidden', 328 '#value' => -1, 329 '#prefix' => '<div id="edit-book-plid-wrapper">', 330 '#suffix' => '</div>', 331 ); 332 333 if ($book_link['nid'] === $book_link['bid']) { 334 // This is a book - at the top level. 335 if ($book_link['original_bid'] === $book_link['bid']) { 336 $form['#prefix'] .= '<em>'. t('This is the top-level page in this book.') .'</em>'; 337 } 338 else { 339 $form['#prefix'] .= '<em>'. t('This will be the top-level page in this book.') .'</em>'; 340 } 341 } 342 elseif (!$book_link['bid']) { 343 $form['#prefix'] .= '<em>'. t('No book selected.') .'</em>'; 344 } 345 else { 346 $form = array( 347 '#type' => 'select', 348 '#title' => t('Parent item'), 349 '#default_value' => $book_link['plid'], 350 '#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)), 351 '#options' => book_toc($book_link['bid'], array($book_link['mlid']), $book_link['parent_depth_limit']), 352 '#attributes' => array('class' => 'book-title-select'), 353 ); 354 } 355 return $form; 356 } 357 358 /** 359 * Build the common elements of the book form for the node and outline forms. 360 */ 361 function _book_add_form_elements(&$form, $node) { 362 // Need this for AJAX. 363 $form['#cache'] = TRUE; 364 drupal_add_js("if (Drupal.jsEnabled) { $(document).ready(function() { $('#edit-book-pick-book').css('display', 'none'); }); }", 'inline'); 365 366 $form['book'] = array( 367 '#type' => 'fieldset', 368 '#title' => t('Book outline'), 369 '#weight' => 10, 370 '#collapsible' => TRUE, 371 '#collapsed' => TRUE, 372 '#tree' => TRUE, 373 '#attributes' => array('class' => 'book-outline-form'), 374 ); 375 foreach (array('menu_name', 'mlid', 'nid', 'router_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) { 376 $form['book'][$key] = array( 377 '#type' => 'value', 378 '#value' => $node->book[$key], 379 ); 380 } 381 382 $form['book']['plid'] = _book_parent_select($node->book); 383 384 $form['book']['weight'] = array( 385 '#type' => 'weight', 386 '#title' => t('Weight'), 387 '#default_value' => $node->book['weight'], 388 '#delta' => 15, 389 '#weight' => 5, 390 '#description' => t('Pages at a given level are ordered first by weight and then by title.'), 391 ); 392 $options = array(); 393 $nid = isset($node->nid) ? $node->nid : 'new'; 394 395 if (isset($node->nid) && ($nid == $node->book['original_bid']) && ($node->book['parent_depth_limit'] == 0)) { 396 // This is the top level node in a maximum depth book and thus cannot be moved. 397 $options[$node->nid] = $node->title; 398 } 399 else { 400 foreach (book_get_books() as $book) { 401 $options[$book['nid']] = $book['title']; 402 } 403 } 404 405 if (user_access('create new books') && ($nid == 'new' || ($nid != $node->book['original_bid']))) { 406 // The node can become a new book, if it is not one already. 407 $options = array($nid => '<'. t('create a new book') .'>') + $options; 408 } 409 if (!$node->book['mlid']) { 410 // The node is not currently in a the hierarchy. 411 $options = array(0 => '<'. t('none') .'>') + $options; 412 } 413 414 // Add a drop-down to select the destination book. 415 $form['book']['bid'] = array( 416 '#type' => 'select', 417 '#title' => t('Book'), 418 '#default_value' => $node->book['bid'], 419 '#options' => $options, 420 '#access' => (bool)$options, 421 '#description' => t('Your page will be a part of the selected book.'), 422 '#weight' => -5, 423 '#attributes' => array('class' => 'book-title-select'), 424 '#ahah' => array( 425 'path' => 'book/js/form', 426 'wrapper' => 'edit-book-plid-wrapper', 427 'effect' => 'slide', 428 ), 429 ); 430 } 431 432 /** 433 * Common helper function to handles additions and updates to the book outline. 434 * 435 * Performs all additions and updates to the book outline through node addition, 436 * node editing, node deletion, or the outline tab. 437 */ 438 function _book_update_outline(&$node) { 439 if (empty($node->book['bid'])) { 440 return FALSE; 441 } 442 $new = empty($node->book['mlid']); 443 444 $node->book['link_path'] = 'node/'. $node->nid; 445 $node->book['link_title'] = $node->title; 446 $node->book['parent_mismatch'] = FALSE; // The normal case. 447 448 if ($node->book['bid'] == $node->nid) { 449 $node->book['plid'] = 0; 450 $node->book['menu_name'] = book_menu_name($node->nid); 451 } 452 else { 453 // Check in case the parent is not is this book; the book takes precedence. 454 if (!empty($node->book['plid'])) { 455 $parent = db_fetch_array(db_query("SELECT * FROM {book} WHERE mlid = %d", $node->book['plid'])); 456 } 457 if (empty($node->book['plid']) || !$parent || $parent['bid'] != $node->book['bid']) { 458 $node->book['plid'] = db_result(db_query("SELECT mlid FROM {book} WHERE nid = %d", $node->book['bid'])); 459 $node->book['parent_mismatch'] = TRUE; // Likely when JS is disabled. 460 } 461 } 462 if (menu_link_save($node->book)) { 463 if ($new) { 464 // Insert new. 465 db_query("INSERT INTO {book} (nid, mlid, bid) VALUES (%d, %d, %d)", $node->nid, $node->book['mlid'], $node->book['bid']); 466 } 467 else { 468 if ($node->book['bid'] != db_result(db_query("SELECT bid FROM {book} WHERE nid = %d", $node->nid))) { 469 // Update the bid for this page and all children. 470 book_update_bid($node->book); 471 } 472 } 473 return TRUE; 474 } 475 // Failed to save the menu link 476 return FALSE; 477 } 478 479 /** 480 * Update the bid for a page and its children when it is moved to a new book. 481 * 482 * @param $book_link 483 * A fully loaded menu link that is part of the book hierarchy. 484 */ 485 function book_update_bid($book_link) { 486 487 for ($i = 1; $i <= MENU_MAX_DEPTH && $book_link["p$i"]; $i++) { 488 $match[] = "p$i = %d"; 489 $args[] = $book_link["p$i"]; 490 } 491 $result = db_query("SELECT mlid FROM {menu_links} WHERE ". implode(' AND ', $match), $args); 492 493 $mlids = array(); 494 while ($a = db_fetch_array($result)) { 495 $mlids[] = $a['mlid']; 496 } 497 if ($mlids) { 498 db_query("UPDATE {book} SET bid = %d WHERE mlid IN (". implode(',', $mlids) .")", $book_link['bid']); 499 } 500 } 501 502 /** 503 * Get the book menu tree for a page, and return it as a linear array. 504 * 505 * @param $book_link 506 * A fully loaded menu link that is part of the book hierarchy. 507 * @return 508 * A linear array of menu links in the order that the links are shown in the 509 * menu, so the previous and next pages are the elements before and after the 510 * element corresponding to $node. The children of $node (if any) will come 511 * immediately after it in the array. 512 */ 513 function book_get_flat_menu($book_link) { 514 static $flat = array(); 515 516 if (!isset($flat[$book_link['mlid']])) { 517 // Call menu_tree_all_data() to take advantage of the menu system's caching. 518 $tree = menu_tree_all_data($book_link['menu_name'], $book_link); 519 $flat[$book_link['mlid']] = array(); 520 _book_flatten_menu($tree, $flat[$book_link['mlid']]); 521 } 522 return $flat[$book_link['mlid']]; 523 } 524 525 /** 526 * Recursive helper function for book_get_flat_menu(). 527 */ 528 function _book_flatten_menu($tree, &$flat) { 529 foreach ($tree as $data) { 530 if (!$data['link']['hidden']) { 531 $flat[$data['link']['mlid']] = $data['link']; 532 if ($data['below']) { 533 _book_flatten_menu($data['below'], $flat); 534 } 535 } 536 } 537 } 538 539 /** 540 * Fetches the menu link for the previous page of the book. 541 */ 542 function book_prev($book_link) { 543 // If the parent is zero, we are at the start of a book. 544 if ($book_link['plid'] == 0) { 545 return NULL; 546 } 547 $flat = book_get_flat_menu($book_link); 548 // Assigning the array to $flat resets the array pointer for use with each(). 549 $curr = NULL; 550 do { 551 $prev = $curr; 552 list($key, $curr) = each($flat); 553 } while ($key && $key != $book_link['mlid']); 554 555 if ($key == $book_link['mlid']) { 556 // The previous page in the book may be a child of the previous visible link. 557 if ($prev['depth'] == $book_link['depth'] && $prev['has_children']) { 558 // The subtree will have only one link at the top level - get its data. 559 $data = array_shift(book_menu_subtree_data($prev)); 560 // The link of interest is the last child - iterate to find the deepest one. 561 while ($data['below']) { 562 $data = end($data['below']); 563 } 564 return $data['link']; 565 } 566 else { 567 return $prev; 568 } 569 } 570 } 571 572 /** 573 * Fetches the menu link for the next page of the book. 574 */ 575 function book_next($book_link) { 576 $flat = book_get_flat_menu($book_link); 577 // Assigning the array to $flat resets the array pointer for use with each(). 578 do { 579 list($key, $curr) = each($flat); 580 } while ($key && $key != $book_link['mlid']); 581 if ($key == $book_link['mlid']) { 582 return current($flat); 583 } 584 } 585 586 /** 587 * Format the menu links for the child pages of the current page. 588 */ 589 function book_children($book_link) { 590 $flat = book_get_flat_menu($book_link); 591 592 $children = array(); 593 594 if ($book_link['has_children']) { 595 // Walk through the array until we find the current page. 596 do { 597 $link = array_shift($flat); 598 } while ($link && ($link['mlid'] != $book_link['mlid'])); 599 // Continue though the array and collect the links whose parent is this page. 600 while (($link = array_shift($flat)) && $link['plid'] == $book_link['mlid']) { 601 $data['link'] = $link; 602 $data['below'] = ''; 603 $children[] = $data; 604 } 605 } 606 return $children ? menu_tree_output($children) : ''; 607 } 608 609 /** 610 * Generate the corresponding menu name from a book ID. 611 */ 612 function book_menu_name($bid) { 613 return 'book-toc-'. $bid; 614 } 615 616 /** 617 * Build an active trail to show in the breadcrumb. 618 */ 619 function book_build_active_trail($book_link) { 620 static $trail; 621 622 if (!isset($trail)) { 623 $trail = array(); 624 $trail[] = array('title' => t('Home'), 'href' => '<front>', 'localized_options' => array()); 625 626 $tree = menu_tree_all_data($book_link['menu_name'], $book_link); 627 $curr = array_shift($tree); 628 629 while ($curr) { 630 if ($curr['link']['href'] == $book_link['href']) { 631 $trail[] = $curr['link']; 632 $curr = FALSE; 633 } 634 else { 635 if ($curr['below'] && $curr['link']['in_active_trail']) { 636 $trail[] = $curr['link']; 637 $tree = $curr['below']; 638 } 639 $curr = array_shift($tree); 640 } 641 } 642 } 643 return $trail; 644 } 645 646 /** 647 * Implementation of hook_nodeapi(). 648 * 649 * Appends book navigation to all nodes in the book, and handles book outline 650 * insertions and updates via the node form. 651 */ 652 function book_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { 653 switch ($op) { 654 case 'load': 655 // Note - we cannot use book_link_load() because it will call node_load() 656 $info['book'] = db_fetch_array(db_query('SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid = %d', $node->nid)); 657 if ($info['book']) { 658 $info['book']['href'] = $info['book']['link_path']; 659 $info['book']['title'] = $info['book']['link_title']; 660 $info['book']['options'] = unserialize($info['book']['options']); 661 return $info; 662 } 663 break; 664 case 'view': 665 if (!$teaser) { 666 if (!empty($node->book['bid']) && $node->build_mode == NODE_BUILD_NORMAL) { 667 668 $node->content['book_navigation'] = array( 669 '#value' => theme('book_navigation', $node->book), 670 '#weight' => 100, 671 ); 672 673 if ($page) { 674 menu_set_active_trail(book_build_active_trail($node->book)); 675 menu_set_active_menu_name($node->book['menu_name']); 676 } 677 } 678 } 679 break; 680 case 'presave': 681 // Always save a revision for non-administrators. 682 if (!empty($node->book['bid']) && !user_access('administer nodes')) { 683 $node->revision = 1; 684 } 685 // Make sure a new node gets a new menu link. 686 if (empty($node->nid)) { 687 $node->book['mlid'] = NULL; 688 } 689 break; 690 case 'insert': 691 case 'update': 692 if (!empty($node->book['bid'])) { 693 if ($node->book['bid'] == 'new') { 694 // New nodes that are their own book. 695 $node->book['bid'] = $node->nid; 696 } 697 $node->book['nid'] = $node->nid; 698 $node->book['menu_name'] = book_menu_name($node->book['bid']); 699 _book_update_outline($node); 700 } 701 break; 702 case 'delete': 703 if (!empty($node->book['bid'])) { 704 if ($node->nid == $node->book['bid']) { 705 // Handle deletion of a top-level post. 706 $result = db_query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = %d", $node->book['mlid']); 707 while ($child = db_fetch_array($result)) { 708 $child_node = node_load($child['nid']); 709 $child_node->book['bid'] = $child_node->nid; 710 _book_update_outline($child_node); 711 } 712 } 713 menu_link_delete($node->book['mlid']); 714 db_query('DELETE FROM {book} WHERE mlid = %d', $node->book['mlid']); 715 } 716 break; 717 case 'prepare': 718 // Prepare defaults for the add/edit form. 719 if (empty($node->book) && (user_access('add content to books') || user_access('administer book outlines'))) { 720 $node->book = array(); 721 if (empty($node->nid) && isset($_GET['parent']) && is_numeric($_GET['parent'])) { 722 // Handle "Add child page" links: 723 $parent = book_link_load($_GET['parent']); 724 if ($parent && $parent['access']) { 725 $node->book['bid'] = $parent['bid']; 726 $node->book['plid'] = $parent['mlid']; 727 $node->book['menu_name'] = $parent['menu_name']; 728 } 729 } 730 // Set defaults. 731 $node->book += _book_link_defaults(!empty($node->nid) ? $node->nid : 'new'); 732 } 733 else { 734 if (isset($node->book['bid']) && !isset($node->book['original_bid'])) { 735 $node->book['original_bid'] = $node->book['bid']; 736 } 737 } 738 // Find the depth limit for the parent select. 739 if (isset($node->book['bid']) && !isset($node->book['parent_depth_limit'])) { 740 $node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book); 741 } 742 break; 743 } 744 } 745 746 /** 747 * Find the depth limit for items in the parent select. 748 */ 749 function _book_parent_depth_limit($book_link) { 750 return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? menu_link_children_relative_depth($book_link) : 0); 751 } 752 753 /** 754 * Form altering function for the confirm form for a single node deletion. 755 */ 756 function book_form_node_delete_confirm_alter(&$form, $form_state) { 757 758 $node = node_load($form['nid']['#value']); 759 760 if (isset($node->book) && $node->book['has_children']) { 761 $form['book_warning'] = array( 762 '#value' => '<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>', 763 '#weight' => -10, 764 ); 765 } 766 } 767 768 /** 769 * Return an array with default values for a book link. 770 */ 771 function _book_link_defaults($nid) { 772 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()); 773 } 774 775 /** 776 * Process variables for book-navigation.tpl.php. 777 * 778 * The $variables array contains the following arguments: 779 * - $book_link 780 * 781 * @see book-navigation.tpl.php 782 */ 783 function template_preprocess_book_navigation(&$variables) { 784 $book_link = $variables['book_link']; 785 786 // Provide extra variables for themers. Not needed by default. 787 $variables['book_id'] = $book_link['bid']; 788 $variables['book_title'] = check_plain($book_link['link_title']); 789 $variables['book_url'] = 'node/'. $book_link['bid']; 790 $variables['current_depth'] = $book_link['depth']; 791 792 $variables['tree'] = ''; 793 if ($book_link['mlid']) { 794 $variables['tree'] = book_children($book_link); 795 796 if ($prev = book_prev($book_link)) { 797 $prev_href = url($prev['href']); 798 drupal_add_link(array('rel' => 'prev', 'href' => $prev_href)); 799 $variables['prev_url'] = $prev_href; 800 $variables['prev_title'] = check_plain($prev['title']); 801 } 802 if ($book_link['plid'] && $parent = book_link_load($book_link['plid'])) { 803 $parent_href = url($parent['href']); 804 drupal_add_link(array('rel' => 'up', 'href' => $parent_href)); 805 $variables['parent_url'] = $parent_href; 806 $variables['parent_title'] = check_plain($parent['title']); 807 } 808 if ($next = book_next($book_link)) { 809 $next_href = url($next['href']); 810 drupal_add_link(array('rel' => 'next', 'href' => $next_href)); 811 $variables['next_url'] = $next_href; 812 $variables['next_title'] = check_plain($next['title']); 813 } 814 } 815 816 $variables['has_links'] = FALSE; 817 // Link variables to filter for values and set state of the flag variable. 818 $links = array('prev_url', 'prev_title', 'parent_url', 'parent_title', 'next_url', 'next_title'); 819 foreach ($links as $link) { 820 if (isset($variables[$link])) { 821 // Flag when there is a value. 822 $variables['has_links'] = TRUE; 823 } 824 else { 825 // Set empty to prevent notices. 826 $variables[$link] = ''; 827 } 828 } 829 } 830 831 /** 832 * A recursive helper function for book_toc(). 833 */ 834 function _book_toc_recurse($tree, $indent, &$toc, $exclude, $depth_limit) { 835 foreach ($tree as $data) { 836 if ($data['link']['depth'] > $depth_limit) { 837 // Don't iterate through any links on this level. 838 break; 839 } 840 if (!in_array($data['link']['mlid'], $exclude)) { 841 $toc[$data['link']['mlid']] = $indent .' '. truncate_utf8($data['link']['title'], 30, TRUE, TRUE); 842 if ($data['below']) { 843 _book_toc_recurse($data['below'], $indent .'--', $toc, $exclude, $depth_limit); 844 } 845 } 846 } 847 } 848 849 /** 850 * Returns an array of book pages in table of contents order. 851 * 852 * @param $bid 853 * The ID of the book whose pages are to be listed. 854 * @param $exclude 855 * Optional array of mlid values. Any link whose mlid is in this array 856 * will be excluded (along with its children). 857 * @param $depth_limit 858 * Any link deeper than this value will be excluded (along with its children). 859 * @return 860 * An array of mlid, title pairs for use as options for selecting a book page. 861 */ 862 function book_toc($bid, $exclude = array(), $depth_limit) { 863 864 $tree = menu_tree_all_data(book_menu_name($bid)); 865 $toc = array(); 866 _book_toc_recurse($tree, '', $toc, $exclude, $depth_limit); 867 868 return $toc; 869 } 870 871 /** 872 * Process variables for book-export-html.tpl.php. 873 * 874 * The $variables array contains the following arguments: 875 * - $title 876 * - $contents 877 * - $depth 878 * 879 * @see book-export-html.tpl.php 880 */ 881 function template_preprocess_book_export_html(&$variables) { 882 global $base_url, $language; 883 884 $variables['title'] = check_plain($variables['title']); 885 $variables['base_url'] = $base_url; 886 $variables['language'] = $language; 887 $variables['language_rtl'] = ($language->direction == LANGUAGE_RTL); 888 $variables['head'] = drupal_get_html_head(); 889 } 890 891 /** 892 * Traverse the book tree to build printable or exportable output. 893 * 894 * During the traversal, the $visit_func() callback is applied to each 895 * node, and is called recursively for each child of the node (in weight, 896 * title order). 897 * 898 * @param $tree 899 * A subtree of the book menu hierarchy, rooted at the current page. 900 * @param $visit_func 901 * A function callback to be called upon visiting a node in the tree. 902 * @return 903 * The output generated in visiting each node. 904 */ 905 function book_export_traverse($tree, $visit_func) { 906 $output = ''; 907 908 foreach ($tree as $data) { 909 // Note- access checking is already performed when building the tree. 910 if ($node = node_load($data['link']['nid'], FALSE)) { 911 $children = ''; 912 if ($data['below']) { 913 $children = book_export_traverse($data['below'], $visit_func); 914 } 915 916 if (function_exists($visit_func)) { 917 $output .= call_user_func($visit_func, $node, $children); 918 } 919 else { 920 // Use the default function. 921 $output .= book_node_export($node, $children); 922 } 923 } 924 } 925 return $output; 926 } 927 928 /** 929 * Generates printer-friendly HTML for a node. 930 * 931 * @see book_export_traverse() 932 * 933 * @param $node 934 * The node to generate output for. 935 * @param $children 936 * All the rendered child nodes within the current node. 937 * @return 938 * The HTML generated for the given node. 939 */ 940 function book_node_export($node, $children = '') { 941 942 $node->build_mode = NODE_BUILD_PRINT; 943 $node = node_build_content($node, FALSE, FALSE); 944 $node->body = drupal_render($node->content); 945 946 return theme('book_node_export_html', $node, $children); 947 } 948 949 /** 950 * Process variables for book-node-export-html.tpl.php. 951 * 952 * The $variables array contains the following arguments: 953 * - $node 954 * - $children 955 * 956 * @see book-node-export-html.tpl.php 957 */ 958 function template_preprocess_book_node_export_html(&$variables) { 959 $variables['depth'] = $variables['node']->book['depth']; 960 $variables['title'] = check_plain($variables['node']->title); 961 $variables['content'] = $variables['node']->body; 962 } 963 964 /** 965 * Determine if a given node type is in the list of types allowed for books. 966 */ 967 function book_type_is_allowed($type) { 968 return in_array($type, variable_get('book_allowed_types', array('book'))); 969 } 970 971 /** 972 * Implementation of hook_node_type(). 973 * 974 * Update book module's persistent variables if the machine-readable name of a 975 * node type is changed. 976 */ 977 function book_node_type($op, $type) { 978 979 switch ($op) { 980 case 'update': 981 if (!empty($type->old_type) && $type->old_type != $type->type) { 982 // Update the list of node types that are allowed to be added to books. 983 $allowed_types = variable_get('book_allowed_types', array('book')); 984 $key = array_search($type->old_type, $allowed_types); 985 if ($key !== FALSE) { 986 $allowed_types[$type->type] = $allowed_types[$key] ? $type->type : 0; 987 unset($allowed_types[$key]); 988 variable_set('book_allowed_types', $allowed_types); 989 } 990 // Update the setting for the "Add child page" link. 991 if (variable_get('book_child_type', 'book') == $type->old_type) { 992 variable_set('book_child_type', $type->type); 993 } 994 } 995 break; 996 } 997 } 998 999 /** 1000 * Implementation of hook_help(). 1001 */ 1002 function book_help($path, $arg) { 1003 switch ($path) { 1004 case 'admin/help#book': 1005 $output = '<p>'. t('The book module 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>'; 1006 $output .= '<p>'. t('Pages in the book hierarchy have navigation elements at the bottom of the page for moving through the text. These links lead to the previous and next pages in the book, and to the level above the current page in the book\'s structure. More comprehensive navigation may be provided by enabling the <em>book navigation block</em> on the <a href="@admin-block">blocks administration page</a>.', array('@admin-block' => url('admin/build/block'))) .'</p>'; 1007 $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>'; 1008 $output .= '<p>'. t("Users with the <em>administer book outlines</em> permission can add a post of any content type to a book, by selecting the appropriate book while editing the post or by using the interface available on the post's <em>outline</em> tab.") .'</p>'; 1009 $output .= '<p>'. t('Administrators can view a list of all books on the <a href="@admin-node-book">book administration page</a>. The <em>Outline</em> page for each book allows section titles to be edited or rearranged.', array('@admin-node-book' => url('admin/content/book'))) .'</p>'; 1010 $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@book">Book module</a>.', array('@book' => 'http://drupal.org/handbook/modules/book/')) .'</p>'; 1011 return $output; 1012 case 'admin/content/book': 1013 return '<p>'. t('The book module offers a means to organize a collection of related posts, collectively known as a book. When viewed, these posts automatically display links to adjacent book pages, providing a simple navigation system for creating and reviewing structured content.') .'</p>'; 1014 case 'node/%/outline': 1015 return '<p>'. t('The outline feature allows you to include posts in the <a href="@book">book hierarchy</a>, as well as move them within the hierarchy or to <a href="@book-admin">reorder an entire book</a>.', array('@book' => url('book'), '@book-admin' => url('admin/content/book'))) .'</p>'; 1016 } 1017 } 1018 1019 /** 1020 * Like menu_link_load(), but adds additional data from the {book} table. 1021 * 1022 * Do not call when loading a node, since this function may call node_load(). 1023 */ 1024 function book_link_load($mlid) { 1025 if ($item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) { 1026 _menu_link_translate($item); 1027 return $item; 1028 } 1029 return FALSE; 1030 } 1031 1032 /** 1033 * Get the data representing a subtree of the book hierarchy. 1034 * 1035 * The root of the subtree will be the link passed as a parameter, so the 1036 * returned tree will contain this item and all its descendents in the menu tree. 1037 * 1038 * @param $item 1039 * A fully loaded menu link. 1040 * @return 1041 * An subtree of menu links in an array, in the order they should be rendered. 1042 */ 1043 function book_menu_subtree_data($item) { 1044 static $tree = array(); 1045 1046 // Generate a cache ID (cid) specific for this $menu_name and $item. 1047 $cid = 'links:'. $item['menu_name'] .':subtree-cid:'. $item['mlid']; 1048 1049 if (!isset($tree[$cid])) { 1050 $cache = cache_get($cid, 'cache_menu'); 1051 if ($cache && isset($cache->data)) { 1052 // If the cache entry exists, it will just be the cid for the actual data. 1053 // This avoids duplication of large amounts of data. 1054 $cache = cache_get($cache->data, 'cache_menu'); 1055 if ($cache && isset($cache->data)) { 1056 $data = $cache->data; 1057 } 1058 } 1059 // If the subtree data was not in the cache, $data will be NULL. 1060 if (!isset($data)) { 1061 $match = array("menu_name = '%s'"); 1062 $args = array($item['menu_name']); 1063 $i = 1; 1064 while ($i <= MENU_MAX_DEPTH && $item["p$i"]) { 1065 $match[] = "p$i = %d"; 1066 $args[] = $item["p$i"]; 1067 $i++; 1068 } 1069 $sql = " 1070 SELECT b.*, m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, ml.* 1071 FROM {menu_links} ml INNER JOIN {menu_router} m ON m.path = ml.router_path 1072 INNER JOIN {book} b ON ml.mlid = b.mlid 1073 WHERE ". implode(' AND ', $match) ." 1074 ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC"; 1075 1076 $data['tree'] = menu_tree_data(db_query($sql, $args), array(), $item['depth']); 1077 $data['node_links'] = array(); 1078 menu_tree_collect_node_links($data['tree'], $data['node_links']); 1079 // Compute the real cid for book subtree data. 1080 $tree_cid = 'links:'. $item['menu_name'] .':subtree-data:'. md5(serialize($data)); 1081 // Cache the data, if it is not already in the cache. 1082 if (!cache_get($tree_cid, 'cache_menu')) { 1083 cache_set($tree_cid, $data, 'cache_menu'); 1084 } 1085 // Cache the cid of the (shared) data using the menu and item-specific cid. 1086 cache_set($cid, $tree_cid, 'cache_menu'); 1087 } 1088 // Check access for the current user to each item in the tree. 1089 menu_tree_check_access($data['tree'], $data['node_links']); 1090 $tree[$cid] = $data['tree']; 1091 } 1092 1093 return $tree[$cid]; 1094 } 1095
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |