web/drupal/modules/book/book.module
branchdrupal
changeset 74 0ff3ba646492
equal deleted inserted replaced
73:fcf75e232c5b 74:0ff3ba646492
       
     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