[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

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

   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  


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7