You are here

book_access.module in Book access 5

Allows access control for Drupal book nodes.

Based on forum_access.module and tac_lite.module.

File

book_access.module
View source
<?php

/**
 * @file
 *
 * Allows access control for Drupal book nodes.
 *
 * Based on forum_access.module and tac_lite.module.
 *
 *
 */
define(BOOK_ACCESS_GRANT_PRIORITY, 0);

/**
 * Implements hook_help()
 */
function book_access_help($section) {
  switch ($section) {
    case 'admin/content/book/access':
      return '<p>' . t('
        Configures the access control per books based on user roles. Settings
        affect all pages within the given book. If a page is moved into a
        different book, it will assume that book access control settings.</p>
        <p><em>Important:</em> If you are going to manage access control here,
        please disable the <q>edit book pages</q> and <q>edit own book pages</q>
        permissions in the <a href="@access-control">access control</a> page or
        else you may see unexpected behavior.</p>
        <p>These settings will have no effect for roles with <q>administer nodes</q>
        permission.</p>
        <p>For more information, see the <a href="@book-access-help">Book Access help</a> page.
      ' . '</p>', array(
        '@book-access-help' => url('admin/help/book_access'),
        '@access-control' => url('admin/user/access'),
      ));
      break;
    case 'admin/help#book_access':
      return '<p>' . t('
        Allows fine grained access control for books.</p>
        <p>Permissions enabled in the <a href="@access-control-settings">access control settings page</a> will
        override the <a href="@book-access-settings">book access settings</a>. So, for example, if you would like a role to be
        able to edit all book pages, regardless, enable <q>edit pages</q> in
        the access control settings page. However, if you would like to control edit
        permission on a per book basis, disable that permission in
        the access control settings page and configure the book access settings
        accordingly.
        <p>Certain access control modules can impact functionality of this
        module. Broad reaching modules such as <q>taxonomy access</q> and <q>content
        access</q> can override the values set in the <a href="@book-access-settings">book access settings</a> page.
        You must turn off all enabled access controls in such modules.
        ' . '</p>', array(
        '@book-access-settings' => url('admin/content/book/access'),
        '@access-control-settings' => url('admin/user/access'),
      ));
      break;
  }
}

/**
 * Implements hook_perm().
 */
function book_access_perm() {
  return array(
    'administer book access',
  );
}

/**
 * Implements hook_menu().
 */
function book_access_menu($may_cache) {
  if ($may_cache) {

    // We create an additional tab in the book admin page.
    $items[] = array(
      'path' => 'admin/content/book/access',
      'title' => t('Access control'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'book_access_admin_form',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => 7,
      'access' => user_access('administer book access'),
    );
  }
  return $items;
}

/**
 * Implements hook_node_grants().
 *
 * This function supplies the book access grants. book_access simply uses
 * roles as grant IDs.
 */
function book_access_node_grants($user, $op) {
  $grants['book_access'] = array_keys($user->roles);
  return $grants;
}

/**
 * Implements hook_node_access_records().
 *
 * Returns a list of grant records for the passed in book node object. If we
 * have a book child page, we return the access settings of the top level parent.
 * Checks to see if maybe we're being disabled.
 */
function book_access_node_access_records($node) {
  if (!book_access_enabled()) {
    return;
  }
  if ($node->type == 'book') {
    $parent_nid = _book_access_get_book_nid($node->nid);
    $result = db_query('SELECT * FROM {book_access} WHERE nid = %d', $parent_nid);
    while ($grant = db_fetch_object($result)) {
      $grants[] = array(
        'realm' => 'book_access',
        'gid' => $grant->rid,
        'grant_view' => $grant->grant_view,
        'grant_update' => $grant->grant_update,
        'grant_delete' => $grant->grant_delete,
        'priority' => BOOK_ACCESS_GRANT_PRIORITY,
      );
    }
    return $grants;
  }
}

/**
 * Implements hook_form_alter().
 *
 */
function book_access_form_alter($form_id, &$form) {
  if ($form_id == 'book_node_form' && isset($form['parent'])) {
    _book_access_restrict_options($form['parent']['#options']);
  }

  // When an outline is modified, taxonomy is changed, but the node is not
  // saved, so node grants can become broken if a book page is moved into
  // another book. so we fix that by adding an additional #submit callback
  // that rebuilds the grants when the book outline is modified.
  if ($form_id == 'book_outline_form') {
    $form['#submit'][] = '_book_access_build_node_grants';
  }
}

/**
 * Book access configuration page.
 *
 */
function book_access_admin_form() {
  $form = array();
  $rids = array();
  $books = array();
  drupal_add_css(drupal_get_path('module', 'book_access') . '/book_access.css');

  // Get a list of roles (which act as grant IDs).
  $results = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
  while ($result = db_fetch_object($results)) {
    $rids[$result->rid] = $result->name;
  }

  // Get listing of books, each of which will have it's own access settings.
  $sql = "\n    SELECT n.nid, n.title\n    FROM {node} n\n    LEFT JOIN {book} b ON n.vid = b.vid\n    WHERE b.parent = 0\n    ORDER BY b.weight ASC\n  ";
  $book_results = db_query($sql);
  while ($book = db_fetch_object($book_results)) {
    $books[$book->nid] = $book->title;
  }

  // Each book has its own access control settings.
  foreach ($books as $book_nid => $book_name) {

    // Used to store existing grants for this book.
    $view = array();
    $update = array();
    $delete = array();
    $result = db_query("SELECT * FROM {book_access} where nid = %d", $book_nid);

    // if no existing grants, use some safe defaults
    if (db_num_rows($result) == 0) {
      $view = array(
        1,
        2,
      );
      $update = array();
      $delete = array();
    }
    else {
      while ($book_access = db_fetch_object($result)) {
        if ($book_access->grant_view) {
          $view[] = $book_access->rid;
        }
        if ($book_access->grant_update) {
          $update[] = $book_access->rid;
        }
        if ($book_access->grant_delete) {
          $delete[] = $book_access->rid;
        }
      }
    }
    $form['#tree'] = TRUE;
    $form['access'][$book_nid] = array(
      '#type' => 'fieldset',
      '#title' => $book_name,
      '#collapsible' => TRUE,
    );
    $form['access'][$book_nid]['view'] = array(
      '#type' => 'checkboxes',
      '#prefix' => '<div class="book-access-div">',
      '#suffix' => '</div>',
      '#options' => $rids,
      '#title' => t('View this book'),
      '#default_value' => $view,
    );
    $form['access'][$book_nid]['update'] = array(
      '#type' => 'checkboxes',
      '#prefix' => '<div class="book-access-div">',
      '#suffix' => '</div>',
      '#options' => $rids,
      '#title' => t('Edit pages in this book'),
      '#default_value' => $update,
    );
    $form['access'][$book_nid]['delete'] = array(
      '#type' => 'checkboxes',
      '#prefix' => '<div class="book-access-div">',
      '#suffix' => '</div>',
      '#options' => $rids,
      '#title' => t('Delete pages in this book'),
      '#default_value' => $delete,
    );
  }
  $form['clearer'] = array(
    '#value' => '<div class="book-access-clearer"></div>',
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  $form['notice'] = array(
    '#type' => 'markup',
    '#value' => '<p>' . t('Node access tables must be rebuilt when these changes are
      submitted. This may take a few moments.') . '</p>',
  );
  return $form;
}
function book_access_admin_form_submit($form_id, $form_values) {
  foreach ($form_values['access'] as $book_nid => $form) {
    db_query("DELETE FROM {book_access} WHERE nid = %d", $book_nid);
    foreach ($form['view'] as $rid => $checked) {
      $gid = $rid;
      $grant_view = (bool) $checked;
      $grant_update = $form['update'][$rid] > 0 ? TRUE : FALSE;
      $grant_delete = $form['delete'][$rid] > 0 ? TRUE : FALSE;
      $sql = "INSERT INTO {book_access} (nid, rid, grant_view, grant_update, grant_delete)\n        VALUES (%d, %d, %d, %d, %d)";
      db_query($sql, $book_nid, $rid, $grant_view, $grant_update, $grant_delete);
    }
  }
  node_access_rebuild();
}

/**
 * Helper function.
 */
function book_access_enabled($set = NULL) {
  static $enabled = TRUE;
  if (isset($set)) {
    $enabled = $set;
  }
  return $enabled;
}

/**
 * Returns the array of book nodes and their parents.
 */
function _book_access_get_parents() {
  static $parents = array();
  if (empty($parents)) {
    $sql = "SELECT n.nid, b.parent\n      FROM {node} n\n      LEFT JOIN {book} b ON n.vid = b.vid\n      WHERE n.type = 'book'";
    $query = db_query($sql);
    while ($result = db_fetch_object($query)) {
      $parents[$result->nid] = $result->parent;
    }
  }
  return $parents;
}

/**
 * Somewhat redundant node grants function to allow us to set a node's grants
 * when a book outline is modified.
 */
function _book_access_build_node_grants($form) {
  $node = $form['#node'];
  $grants = book_access_node_access_records($node);
  node_access_write_grants($node, $grants, 'book_access');
}

/**
 * Returns the very top level (book) nid for a given book page.
 */
function _book_access_get_book_nid($nid) {
  $parents = _book_access_get_parents();
  if ($parents[$nid] == 0) {
    return $nid;
  }
  return _book_access_get_book_nid($parents[$nid]);
}

/**
 * We don't want users to be able to add child pages to pages they do not
 * have 'update' grants for, so we remove select options which point to book
 * pages user does not have that grant for.
 */
function _book_access_restrict_options(&$options) {
  global $user;
  $permitted_nids = NULL;
  if ($user->uid == 0 || user_access('administer nodes')) {
    return;
  }
  $sql = "SELECT nid\n    FROM {node_access}\n    WHERE realm = 'book_access'\n    AND gid IN (%s)\n    AND grant_update > 0";
  $results = db_query($sql, implode(',', array_keys($user->roles)));
  while ($result = db_fetch_object($results)) {
    $permitted_nids[$result->nid] = $result->nid;
  }
  if (!empty($options)) {
    foreach ($options as $nid => $value) {
      if ($nid > 0 && !isset($permitted_nids[$nid])) {
        unset($options[$nid]);
      }
    }
  }
}

Functions

Namesort descending Description
book_access_admin_form Book access configuration page.
book_access_admin_form_submit
book_access_enabled Helper function.
book_access_form_alter Implements hook_form_alter().
book_access_help Implements hook_help()
book_access_menu Implements hook_menu().
book_access_node_access_records Implements hook_node_access_records().
book_access_node_grants Implements hook_node_grants().
book_access_perm Implements hook_perm().
_book_access_build_node_grants Somewhat redundant node grants function to allow us to set a node's grants when a book outline is modified.
_book_access_get_book_nid Returns the very top level (book) nid for a given book page.
_book_access_get_parents Returns the array of book nodes and their parents.
_book_access_restrict_options We don't want users to be able to add child pages to pages they do not have 'update' grants for, so we remove select options which point to book pages user does not have that grant for.