[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/modules/book/ -> book.module (source)

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


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7