book_helper.module in Book helper 7
Same filename and directory in other branches
Improves Drupal's core book module's functionality.
File
book_helper.moduleView 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();
}
}