You are here

book_helper.module in Book helper 7

Same filename and directory in other branches
  1. 6 book_helper.module

Improves Drupal's core book module's functionality.

File

book_helper.module
View source
<?php

/**
 * @file
 * Improves Drupal's core book module's functionality.
 */

/**
 * Implements hook_help().
 */
function book_helper_help($path, $arg) {
  switch ($path) {
    case 'node/%/delete':
      book_helper_display_delete_book_page_warning($arg[1]);
      break;
  }
}

/**
 * Implements hook_permission().
 */
function book_helper_permission() {
  return array(
    'administer own book outlines' => array(
      'title' => t('Administer Own Book Outlines'),
      'description' => t("Allows a books owner to order book pages from the 'Order' tab."),
    ),
  );
}

/**
 * Implements hook_init().
 */
function book_helper_init() {

  // Set main book page in menu tree path.
  if (variable_get('book_helper_menu_tree_set_path', 0) && function_exists('menu_tree_set_path')) {
    $node = menu_get_object();

    // Skip setting the menu tree path if we are on the main page of the book.
    if (isset($node->book) && $node->book['nid'] != $node->book['bid']) {

      // Get active menu names for this node.
      $active_menu_names = db_select('menu_links')
        ->distinct()
        ->fields('menu_links', array(
        'menu_name',
      ))
        ->condition('module', 'menu')
        ->condition('link_path', 'node/' . $node->nid)
        ->execute()
        ->fetchCol();

      // Only set path for core's 'menu' module.
      $menu_names = db_select('menu_links')
        ->distinct()
        ->fields('menu_links', array(
        'menu_name',
      ))
        ->condition('module', 'menu')
        ->orderBy('menu_name')
        ->execute()
        ->fetchCol();
      $path = 'node/' . $node->book['bid'];
      foreach ($menu_names as $menu_name) {

        // Make sure we don't reset the menu tree path if this node (aka page)
        // is already active in this menu.
        if (!in_array($menu_name, $active_menu_names)) {
          menu_tree_set_path($menu_name, $path);
        }
      }
    }
  }
}

/**
 * Implements hook_menu().
 */
function book_helper_menu() {

  // Adds an order tab to book's main node.
  $items['node/%node/order'] = array(
    'title' => 'Order',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'book_helper_admin_edit',
      1,
    ),
    'access callback' => '_book_helper_access',
    'access arguments' => array(
      1,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
    'file' => 'book_helper.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 *
 * Overrides the books outline page.
 *
 * @see book.module
 */
function book_helper_menu_alter(&$items) {

  // Hijack the book.module's 'edit order and titles' page. (admin/content/book/%node)
  // This allows us to add the hide/show checkbox to order book form.
  $items['admin/content/book/%node']['page arguments'] = array(
    'book_helper_admin_edit',
    3,
  );
  $items['admin/content/book/%node']['file'] = 'book_helper.admin.inc';
  $items['admin/content/book/%node']['file path'] = drupal_get_path('module', 'book_helper');

  // Remove outline tab.
  if (variable_get('book_helper_remove_outline', '0') == '1') {
    unset($items['node/%node/outline']);
  }
}

/**
 * Implements hook_menu_link_alter().
 */
function book_helper_menu_link_alter(&$item, $menu) {

  // Book module does not support hidden pages so we need to manually
  // preserve it when a book page is updated outside of the book helper
  // module's node/%/order page.
  if (isset($item['module'], $item['mlid']) && $item['module'] == 'book' && !isset($item['hidden'])) {
    $query = 'SELECT hidden FROM {menu_links} WHERE mlid=:mlid';
    $args = array(
      ':mlid' => $item['mlid'],
    );
    $item['hidden'] = db_query($query, $args)
      ->fetchField() ? 1 : 0;
  }
}

/**
 * Menu callback access; Determine if the order book tab is accessible.
 */
function _book_helper_access($node) {
  global $user;
  $is_node_book = isset($node->book['bid']) && $node->book['bid'] == $node->nid;
  if (!$is_node_book) {
    return FALSE;
  }

  // Allow users to administer their own books using 'administer own book outlines'.
  if (user_access('administer own book outlines') && $node->uid === $user->uid) {
    return TRUE;
  }
  return _book_outline_access($node);
}

/**
 * Implements hook_node_insert().
 */
function book_helper_node_insert($node) {
  book_helper_node_update($node);
}

/**
 * Implements hook_node_update().
 */
function book_helper_node_update($node) {

  // Update the book page's menu link title if it has been customized.
  if (isset($node->book_helper, $node->book_helper['link_title_sync']) && !empty($node->book['bid']) && !empty($node->book_helper['link_title_custom']) && !$node->book_helper['link_title_sync'] && $node->book_helper['link_title_custom'] != $node->book['link_title']) {
    db_update('menu_links')
      ->fields(array(
      'link_title' => $node->book_helper['link_title_custom'],
    ))
      ->condition('mlid', $node->book['mlid'])
      ->execute();
  }
}

/**
 * Implements hook_node_load().
 */
function book_helper_node_load($nodes, $types) {

  // Track customized link title and whether it is sync (equal) to the node's title.
  $query = 'SELECT nid, link_title FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid IN(:nids)';
  $args = array(
    ':nids' => array_keys($nodes),
  );
  $result = db_query($query, $args);
  foreach ($result as $record) {
    if ($record->link_title) {
      $node_title = function_exists('node_parent_title_remove') ? node_parent_title_remove($nodes[$record->nid]->title) : $nodes[$record->nid]->title;
      $nodes[$record->nid]->book_helper = array(
        'link_title_custom' => $record->link_title,
        'link_title_sync' => $record->link_title == $node_title,
      );
    }
  }
}

/**
 * Implements hook_node_view().
 */
function book_helper_node_view($node, $view_mode, $langcode) {

  // Remove book navigation.
  if (variable_get('book_helper_remove_book_navigation', 0) == 1) {
    unset($node->content['book_navigation']);
  }
}

/**
 * Implements hook_node_view_alter().
 */
function book_helper_node_view_alter(&$build) {
  if (empty($build['links']['book']['#links'])) {
    return;
  }
  $book_links =& $build['links']['book']['#links'];
  $book_link_names = array(
    'book_add_child',
    'book_printer',
  );
  $book_helper_links = variable_get('book_helper_links', $book_link_names);
  foreach ($book_link_names as $book_link_name) {
    if (isset($book_links[$book_link_name]) && (empty($book_helper_links) || !in_array($book_link_name, $book_helper_links))) {
      unset($book_links[$book_link_name]);
    }
  }
}

/**
 * Implements hook_block_info().
 */
function book_helper_block_info() {
  $blocks['book-helper-inline-navigation'] = array(
    'info' => t('Book (inline) navigation'),
    'cache' => DRUPAL_CACHE_PER_PAGE,
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function book_helper_block_view($delta = '') {
  $node = menu_get_object();
  if (!empty($node->book['bid'])) {
    return array(
      'content' => theme('book_navigation', array(
        'book_link' => $node->book,
      )),
    );
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function book_helper_form_book_admin_settings_alter(&$form, &$form_state) {
  module_load_include('inc', 'book_helper', 'book_helper.admin');
  _book_helper_form_book_admin_settings_alter($form, $form_state);
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function book_helper_form_node_form_alter(&$form, &$form_state) {
  $node =& $form['#node'];
  $type = $form['type']['#value'];
  if (isset($form['book']) && !book_type_is_allowed($type) && empty($node->book['bid'])) {

    // Hide book fieldset.
    $form['book']['#access'] = FALSE;
  }
  elseif (!isset($node->nid) && !isset($_GET['parent']) && in_array($type, variable_get('book_helper_create_new', array()))) {

    // Create new book for selected nodes that are not being added as child pages.
    $node->book['bid'] == 'new';
    $form['book']['bid']['#default_value'] = 'new';
  }
  if (isset($form['book'])) {

    // Define default book helper properties.
    if (!isset($node->book_helper)) {
      $node->book_helper = array(
        'link_title_custom' => $node->title,
        'link_title_sync' => TRUE,
      );
    }

    // Add inputs to allow user to customize the book link title separately from the node's title.
    drupal_add_js(drupal_get_path('module', 'book_helper') . '/book_helper.js');
    $form['book']['link_title'] = array(
      '#type' => 'textfield',
      '#title' => t('Book link title'),
      '#default_value' => $node->book_helper['link_title_custom'],
      '#maxlength' => 255,
    );
    $form['book']['link_title_sync'] = array(
      '#type' => 'checkbox',
      '#title' => t("Synchronize this node's title with its book link title."),
      '#default_value' => $node->book_helper['link_title_sync'],
    );

    // If not a new node, add button to update book outline only.
    if (isset($node->nid)) {
      $form['book']['book_helper_submit'] = array(
        '#type' => 'submit',
        '#value' => t('Update book outline'),
        '#weight' => 30,
        '#submit' => array(
          'book_helper_outline_submit',
        ),
      );

      // Add button to remove from book.
      // The #access function ensures the button is only displayed where relevant.
      $form['book']['book_helper_remove'] = array(
        '#type' => 'submit',
        '#value' => t('Remove from book'),
        '#access' => _book_node_is_removable($node),
        '#weight' => 35,
        '#submit' => array(
          'book_helper_remove_submit',
        ),
      );
    }
  }
}
function book_helper_outline_submit($form, &$form_state) {
  $node = $form['#node'];
  if (_book_helper_fill_link($node, $form_state['values'])) {
    _book_helper_outline_submit($node);
  }
}
function book_helper_remove_submit($form, &$form_state) {
  $form_state['redirect'] = 'node/' . $form['#node']->nid . '/outline/remove';
}

/**
 * Fill the menu link and custom title values into the node.
 *
 * @return
 *   TRUE if anything was changed, else FALSE
 */
function _book_helper_fill_link($node, &$values) {

  // Populate node from form values.
  if (isset($values['book']['hidden'])) {

    // Value of 'hidden' on the form means "enabled", so is inverse of hidden flag in menu.
    $hidden = $values['book']['hidden'] ? 0 : 1;
  }

  // Set the $changed flag if any value has changed.
  $changed = isset($hidden) && $node->book['hidden'] != $hidden || isset($values['book']['bid']) && $node->book['bid'] != $values['book']['bid'] || $node->book['plid'] != $values['book']['plid'] || $node->book['weight'] != $values['book']['weight'] || $node->book_helper['link_title_sync'] != $values['book']['link_title_sync'] || $node->book_helper['link_title_custom'] != $values['book']['link_title'];

  // If anything changed, set all values.
  if ($changed) {
    if (isset($hidden)) {
      $node->book['hidden'] = $hidden;
    }
    if (isset($values['book']['bid'])) {
      $node->book['bid'] = $values['book']['bid'];
    }
    $node->book['plid'] = $values['book']['plid'];
    $node->book['weight'] = $values['book']['weight'];
    $node->book_helper['link_title_sync'] = $values['book']['link_title_sync'];
    $node->book_helper['link_title_custom'] = $values['book']['link_title'];
  }
  return $changed;
}
function _book_helper_outline_submit($node) {

  // Update the standard book outline.
  _book_update_outline($node);

  // Update the menu link title override.
  book_helper_node_update($node);

  // Clear the menu cache (as advised after invoking menu_link_save(), which is done indirectly above).
  menu_cache_clear_all();
}

/**
 * Display a warning warning when deleting a book page that has child pages.
 */
function book_helper_display_delete_book_page_warning($nid) {
  $node = node_load($nid);
  if (isset($node->book) && $node->book['bid'] != $node->nid) {
    $book_node = node_load($node->book['bid']);
    $query = 'SELECT mlid, link_title AS title, link_path AS href FROM {menu_links} WHERE plid=:plid ORDER BY weight';
    $args = array(
      ':plid' => $node->book['mlid'],
    );
    $result = db_query($query, $args);
    $links = array();
    foreach ($result as $record) {
      $links[$record->mlid] = (array) $record;
    }
    if (empty($links)) {
      return;
    }
    $t_args = array(
      '!book' => l($book_node->title, 'node' . $book_node->nid),
    );
    $output = t('The below book pages are not going to be deleted, they will be moved to the root of the !book book.', $t_args) . '<br />' . t('It is recommended that you delete or move these pages before proceeding.') . theme('links', array(
      'links' => $links,
    ));
    drupal_set_message($output, 'warning');
  }
}

/**
 * Implements hook_theme_registry_alter().
 *
 * Remove template_preprocess_book_navigation();
 * Prevent book_navigation variables from being preprocessed multiple times.
 */
function book_helper_theme_registry_alter(&$theme_registry) {
  if (isset($theme_registry['book_navigation']['preprocess functions']) && is_array($theme_registry['book_navigation']['preprocess functions'])) {
    $theme_registry['book_navigation']['preprocess functions'] = array_diff($theme_registry['book_navigation']['preprocess functions'], array(
      'template_preprocess_book_navigation',
    ));
  }
}

/**
 * Override; Process variables for book-navigation.tpl.php.
 */
function book_helper_preprocess_book_navigation(&$variables) {
  $navigation_options = variable_get('book_helper_navigation_options', array(
    'tree',
    'prev',
    'next',
    'up',
  ));

  // Below code is copied from template_preprocess_book_navigation();
  $book_link = $variables['book_link'];

  // Provide extra variables for themers. Not needed by default.
  $variables['book_id'] = $book_link['bid'];
  $variables['current_depth'] = $book_link['depth'];
  $variables['tree'] = '';
  if ($book_link['mlid'] !== $book_link['p1'] && ($book = book_link_load($book_link['p1']))) {
    $book_href = url($book['href']);
    drupal_add_html_head_link(array(
      'rel' => 'top',
      'href' => $book_href,
    ));
    $variables['book_title'] = check_plain($book['title']);
    $variables['book_url'] = $book_href;
  }
  else {
    $variables['book_title'] = check_plain($book_link['link_title']);
    $variables['book_url'] = url('node/' . $book_link['bid']);
  }
  if ($book_link['mlid']) {
    if (in_array('tree', $navigation_options)) {
      $variables['tree'] = book_children($book_link);
    }
    if (in_array('prev', $navigation_options) && ($prev = book_prev($book_link))) {
      $prev_href = url($prev['href']);
      drupal_add_html_head_link(array(
        'rel' => 'prev',
        'href' => $prev_href,
      ));
      $variables['prev_url'] = $prev_href;
      $variables['prev_title'] = check_plain($prev['title']);
    }
    if (in_array('up', $navigation_options) && $book_link['plid'] && ($parent = book_link_load($book_link['plid']))) {
      $parent_href = url($parent['href']);
      drupal_add_html_head_link(array(
        'rel' => 'up',
        'href' => $parent_href,
      ));
      $variables['parent_url'] = $parent_href;
      $variables['parent_title'] = check_plain($parent['title']);
    }
    if (in_array('next', $navigation_options) && ($next = book_next($book_link))) {
      $next_href = url($next['href']);
      drupal_add_html_head_link(array(
        'rel' => 'next',
        'href' => $next_href,
      ));
      $variables['next_url'] = $next_href;
      $variables['next_title'] = check_plain($next['title']);
    }
  }
  $variables['has_links'] = FALSE;

  // Link variables to filter for values and set state of the flag variable.
  $links = array(
    'prev_url',
    'prev_title',
    'parent_url',
    'parent_title',
    'next_url',
    'next_title',
  );
  foreach ($links as $link) {
    if (isset($variables[$link])) {

      // Flag when there is a value.
      $variables['has_links'] = TRUE;
    }
    else {

      // Set empty to prevent notices.
      $variables[$link] = '';
    }
  }
}

/**
 * Implements hook_theme().
 */
function book_helper_theme() {
  return array(
    'book_helper_admin_table' => array(
      'render element' => 'form',
      'file' => 'book_helper.admin.inc',
    ),
  );
}

/**
 * Implements hook_admin_paths().
 */
function book_helper_admin_paths() {
  if (variable_get('book_helper_admin_theme')) {
    $paths = array(
      'node/*/order' => TRUE,
    );
    return $paths;
  }
}

/**
 * Action to provide an "Add to book" function for VBO.
 *
 * This is similar to VBO's built-in "Move to book", but avoids
 * marking the node as updated.
 *
 * Implements hook_action_info().
 *
 */
function book_helper_action_info() {
  $actions = array();
  $actions['book_helper_action'] = array(
    'type' => 'node',
    'label' => t('Add to book'),
    'vbo_configurable' => TRUE,
    'configurable' => TRUE,
    'behavior' => array(
      'views_property',
    ),
    'triggers' => array(
      'any',
    ),
  );
  return $actions;
}
function book_helper_action_views_bulk_operations_form($options) {

  //   $books = array_column(book_get_books(), 'title');
  $all_books = book_get_books();
  foreach ($all_books as $value) {
    $books[$value['nid']] = $value['title'];
  }
  $form['books'] = array(
    '#type' => 'select',
    '#multiple' => TRUE,
    '#title' => t('Select books to offer'),
    '#options' => $books,
    '#default_value' => !empty($options['books']) ? $options['books'] : array(),
  );
  $form['depth'] = array(
    '#type' => 'select',
    '#title' => t('Page depth'),
    '#options' => array(
      1 => 1,
      2,
      3,
      4,
    ),
    '#default_value' => !empty($options['depth']) ? $options['depth'] : 1,
  );
  return $form;
}
function book_helper_action_form($context) {
  $settings =& $context['settings'];
  $books = $settings['books'];
  $depth = $settings['depth'];
  $all_books = book_get_books();
  $options = array();
  foreach ($all_books as $book) {
    if (empty($books) || in_array($book['nid'], $books)) {
      $options += book_toc($book['nid'], $depth);
    }
  }
  if (empty($options)) {
    drupal_set_message(t('You have no books.'), 'error');
    return array();
  }
  $form['book'] = array(
    '#type' => 'select',
    '#title' => t('Choose a parent book page'),
    '#options' => $options,
    '#description' => t('Select the parent book page you wish to move the book page into'),
  );
  return $form;
}
function book_helper_action_submit($form, $form_state) {
  return array(
    'book' => $form_state['values']['book'],
  );
}
function book_helper_action($node, $context = array()) {
  if (isset($context['book'])) {

    // The selected value from the form is the mlid of the page we want
    // as the parent of this page - i.e. its plid.
    $plid = $context['book'];

    // Use this to lookup the bid in the book table.
    $bid = db_select('book', 'bk')
      ->condition('bk.mlid', $plid)
      ->fields('bk', array(
      'bid',
    ))
      ->execute()
      ->fetchField();
    $node->book['bid'] = $bid;
    $node->book['plid'] = $plid;
    $node->book['module'] = 'book';
    _book_update_outline($node);
    menu_cache_clear_all();
  }
}

Functions

Namesort descending Description
book_helper_action
book_helper_action_form
book_helper_action_info Action to provide an "Add to book" function for VBO.
book_helper_action_submit
book_helper_action_views_bulk_operations_form
book_helper_admin_paths Implements hook_admin_paths().
book_helper_block_info Implements hook_block_info().
book_helper_block_view Implements hook_block_view().
book_helper_display_delete_book_page_warning Display a warning warning when deleting a book page that has child pages.
book_helper_form_book_admin_settings_alter Implements hook_form_FORM_ID_alter().
book_helper_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter().
book_helper_help Implements hook_help().
book_helper_init Implements hook_init().
book_helper_menu Implements hook_menu().
book_helper_menu_alter Implements hook_menu_alter().
book_helper_menu_link_alter Implements hook_menu_link_alter().
book_helper_node_insert Implements hook_node_insert().
book_helper_node_load Implements hook_node_load().
book_helper_node_update Implements hook_node_update().
book_helper_node_view Implements hook_node_view().
book_helper_node_view_alter Implements hook_node_view_alter().
book_helper_outline_submit
book_helper_permission Implements hook_permission().
book_helper_preprocess_book_navigation Override; Process variables for book-navigation.tpl.php.
book_helper_remove_submit
book_helper_theme Implements hook_theme().
book_helper_theme_registry_alter Implements hook_theme_registry_alter().
_book_helper_access Menu callback access; Determine if the order book tab is accessible.
_book_helper_fill_link Fill the menu link and custom title values into the node.
_book_helper_outline_submit