You are here

book_access.module in Book access 6

Allows access control for book nodes on a per book basis. It is based on forum_access.module and tac_lite.module.

File

book_access.module
View source
<?php

/**
 * @file
 *
 * Allows access control for book nodes on a per book basis.
 * It is based on forum_access.module and tac_lite.module.
 */
define('BOOK_ACCESS_GRANT_PRIORITY', 0);

/**
 * Implements hook_form_alter().
 *
 * @see book_outline_form()
 * @see book_access_outline_form_submit()
 * @see book_access_edit_form_submit()
 *
 */
function book_access_form_alter(&$form, &$form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] . '_node_form' == $form_id) {
    if (isset($form['book']['bid']['#options'])) {
      _book_access_options_restrict($form['book']['bid']['#options']);
    }
    $form['#submit'][] = 'book_access_edit_form_submit';
  }
  elseif ($form_id == 'book_outline_form') {
    if (isset($form['book']['bid']['#options'])) {
      _book_access_options_restrict($form['book']['bid']['#options']);
    }
    $form['#submit'][] = 'book_access_outline_form_submit';
    if (isset($form['remove'])) {
      $form['remove']['#submit'][] = 'book_access_edit_form_submit';
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for book_admin_settings().
 */
function book_access_form_book_admin_settings_alter(&$form, &$form_state) {
  $form['book_access_default_roles_access'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Default roles access'),
    '#default_value' => variable_get('book_access_default_roles_access', array()),
    '#options' => array(
      'view' => t('View book'),
      'update' => t('Edit pages'),
      'delete' => t('Delete pages'),
    ),
    '#description' => t('The default access values for the new roles.'),
  );
  $form['book_access_default_users_access'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Default users access'),
    '#default_value' => variable_get('book_access_default_users_access', array()),
    '#options' => array(
      'view' => t('View book'),
      'update' => t('Edit pages'),
      'delete' => t('Delete pages'),
    ),
    '#description' => t('The default access values for the new users.'),
  );
  $form['buttons']['#weight'] = 100;
}

/**
 * Implements hook_help().
 */
function book_access_help($path, $arg) {
  switch ($path) {
    case 'admin/help#book_access':
      $help = '<p>' . t('Allow access control for book nodes on a per book basis.</p>
        <p><a href="@permissions">Permissions enabled</a> will override the
        module access settings. For example, if you would like a role to be able
        to edit all book pages, enable <q>edit any book content</q> in
        <a href="@permissions">Permissions</a>.
        If you would like to control edit permission on a per book basis,
        disable that permission in <a href="@permissions">Permissions</a> and
        configure the module accordingly.', array(
        '@permissions' => url('admin/user/permissions'),
      )) . '</p>' . '<p>' . t('Certain access control modules can impact functionality of this
        module. Broad reaching modules such as "taxonomy access" and "content
        access" can override the values set in the <em>Book access</em> settings page.
        You must turn off all enabled access controls in such modules.</p>
        <p>If you are using additional access control modules, be
        certain that none of them are allowing access to book nodes. The simplest
        way to do this is to limit the types of pages that a book may contain to
        a single node type (such as <q>book page</q>) and unset any access grants
        provided by other modules on that node type configuration page.') . '</p>';
      break;
    case 'node/%/book_access':
      $help = '<p>' . t('Configure access control per book based on users or roles. Settings
        affect all pages within the given book.
        These settings will have no effect for roles with permission to administer nodes.') . '</p>';
      break;
    default:
      $help = '';
  }
  return $help;
}

/**
 * Implements hook_link_alter().
 *
 * Enables the link "Add child page" only for users who have the right
 * permission.
 */
function book_access_link_alter(&$links, $node) {
  global $user;
  if (user_access('administer nodes')) {
    return;
  }
  if (!empty($node->book['bid']) && !empty($links['book_add_child'])) {
    $bool = (user_access('add content to books') && book_access_grant_check($node->book['bid'], 'update') || user_access('administer book outlines')) && node_access('create', variable_get('book_child_type', 'book')) && $node->status == 1 && $node->book['depth'] < MENU_MAX_DEPTH;
    if (!$bool) {
      unset($links['book_add_child']);
    }
  }
}

/**
 * Implements hook_menu().
 */
function book_access_menu() {
  $item = array();

  // We create an additional tab in each top-level book view page.
  $items['node/%node/book_access'] = array(
    'title' => 'Book access',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'book_access_permissions_form',
      1,
    ),
    'access callback' => 'book_access_permissions_form_access',
    'access arguments' => array(
      1,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
    'file' => 'book_access.admin.inc',
  );
  $items['book_access/delete/user_permission/%node/%user'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'book_access_remove_user_permissions',
      3,
      4,
    ),
    'access arguments' => array(
      'administer book access',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'book_access.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 *
 * @see book_access_outline_access()
 * @see book_access_outline_remove_access()
 */
function book_access_menu_alter(&$items) {
  if (isset($items['node/%node/outline'])) {
    $items['node/%node/outline']['access callback'] = 'book_access_outline_access';
  }
  if (isset($items['node/%node/outline/remove'])) {
    $items['node/%node/outline/remove']['access callback'] = 'book_access_outline_remove_access';
  }
}

/**
 * Implements hook_node_access_explain().
 *
 * hook_node_access_explain() is defined in devel_node_access.module, which
 * helps you to figure out how node access works and what permissions are
 * currently granted.
 */
function book_access_node_access_explain($row) {
  static $roles = NULL;
  $result = array();
  if ($row->realm == 'book_access_role') {
    if (!isset($roles)) {
      $roles = user_roles();
    }
    if (isset($roles[$row->gid])) {
      $result = array(
        t('Grants for users of role %role', array(
          '%role' => $roles[$row->gid],
        )),
      );
    }
    else {
      $result = array(
        t('Unknown group ID %gid', array(
          '%gid' => $row->gid,
        )),
      );
    }
  }
  elseif ($row->realm == 'book_access_user') {
    if ($user = user_load(array(
      'uid' => $row->gid,
    ))) {
      $result = array(
        t('Grants for user %username', array(
          '%username' => $user->name,
        )),
      );
    }
    else {
      $result = array(
        t('Unknown user ID %gid', array(
          '%gid' => $row->gid,
        )),
      );
    }
  }
  return $result;
}

/**
 * Implements hook_node_access_records().
 *
 * Returns a list of grant records for the book node object passed as argument.
 * If we have a book child page, we return the access settings of the top level
 * parent book page node.
 */
function book_access_node_access_records($node) {
  $grants = array();
  if (!empty($node->book['bid'])) {
    $bid = $node->book['bid'];
    _book_access_node_access_records_roles($grants, $bid);
    _book_access_node_access_records_users($grants, $bid);
  }
  return $grants;
}

/**
 * Implements hook_node_grants().
 */
function book_access_node_grants($account, $op) {
  $grants['book_access_role'] = array_keys($account->roles);
  $grants['book_access_user'] = array(
    $account->uid,
  );
  return $grants;
}

/**
 * Implements hook_nodeapi().
 */
function book_access_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if ($op == 'delete') {
    db_query("DELETE FROM {book_access_role} WHERE nid = %d", $node->nid);
    db_query("DELETE FROM {book_access_user} WHERE nid = %d", $node->nid);
  }
}

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

/**
 * Implements hook_theme().
 */
function book_access_theme() {
  return array(
    'book_access_permissions_form' => array(
      'arguments' => array(
        'form' => array(),
      ),
      'file' => 'book_access.admin.inc',
    ),
  );
}

/**
 * Implements hook_user().
 */
function book_access_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'delete') {
    db_query("DELETE FROM {book_access_user} WHERE uid = %d", $account->uid);
  }
}

/**
 * Determines if the outline tab is accessible.
 *
 * @see book_access_menu_alter()
 */
function book_access_outline_access($node) {
  if (empty($node->book['bid'])) {
    return FALSE;
  }
  $view_access = node_access('view', $node);
  if (user_access('administer book outlines') && $view_access) {
    return TRUE;
  }
  return book_access_grant_check($node->book['bid'], 'update') && $view_access;
}

/**
 * Determines if the user can remove nodes from the outline.
 *
 * @see book_access_menu_alter()
 */
function book_access_outline_remove_access($node) {
  $bool = !empty($node->book['bid']) && $node->book['bid'] != $node->nid && book_access_outline_access($node);
  return $bool;
}

/**
 * Determines when the book access tab can be shown in the node edit page.
 *
 * @see book_access_menu()
 */
function book_access_permissions_form_access($node) {
  $bool = !empty($node->book['bid']) && user_access('administer book access');
  return $bool;
}

/**
 * Form submission callback for node_form(), and book_outline_form().
 *
 * @see node_form()
 * @see book_outline_form()
 * @see book_access_form_alter()
 */
function book_access_edit_form_submit($form, &$form_state) {
  _book_access_build_node_grants($form['#node']);
}

/**
 * Form submission callback for book_outline_form().
 *
 * @see book_outline_form()
 * @see book_access_form_alter()
 */
function book_access_outline_form_submit($form, &$form_state) {
  $node = $form['#node'];
  if (isset($form['plid']) && $form['plid'] != $form_state['values']['plid']) {
    _book_access_build_node_grants($node);
  }
}

/**
 * Rebuilds the book page grants for the node passed as argument.
 *
 * @param $node
 *   The node for which the grants needs to be rebuilt.
 */
function _book_access_build_node_grants($node) {
  if (!empty($node->nid) && !empty($node->book['bid'])) {
    $grants = array();
    _book_access_node_access_records_roles($grants, $node->book['bid']);
    node_access_write_grants($node, $grants, 'book_access_role');
    $grants = array();
    _book_access_node_access_records_users($grants, $node->book['bid']);
    node_access_write_grants($node, $grants, 'book_access_user');
  }
}

/**
 * Checks if a user has access to the book passed as argument.
 *
 * @param $bid
 *   The ID of the book to check.
 * @param $grant
 *   The permission to check for.
 * @param $account
 *   The user account for which the permission is checked; if it is not passed,
 *   the permission is checked against the current logged in user.
 *
 * @return
 *   TRUE if the user has the permission, FALSE otherwise.
 */
function book_access_grant_check($bid, $grant, $account = NULL) {
  if (!isset($account)) {
    $account = $GLOBALS['user'];
  }
  $roles = array_keys($account->roles);
  $result = db_result(db_query_range("SELECT 1 FROM {book_access_role} WHERE nid = %d AND rid IN (" . db_placeholders($roles) . ") AND grant_{$grant} > 0", array_merge(array(
    $bid,
  ), $roles), 0, 1)) || db_result(db_query_range("SELECT 1 FROM {book_access_user} WHERE nid = %d AND uid = %d AND grant_{$grant} > 0", $bid, $account->uid, 0, 1));
  return $result;
}

/**
 * Gets the list of grant records assigned to user roles for a book.
 *
 * @param $grants
 *   An array of grants.
 * @param $bid
 *   The ID of the book for which the function returns the grant records.
 */
function _book_access_node_access_records_roles(&$grants, $bid) {
  $result = db_query('SELECT * FROM {book_access_role} WHERE nid = %d', $bid);
  while ($grant = db_fetch_object($result)) {
    $grants[] = array(
      'realm' => 'book_access_role',
      'gid' => $grant->rid,
      'grant_view' => $grant->grant_view,
      'grant_update' => $grant->grant_update,
      'grant_delete' => $grant->grant_delete,
      'priority' => BOOK_ACCESS_GRANT_PRIORITY,
    );
  }
}

/**
 * Gets the list of grant records assigned to users for a book.
 *
 * @param $grants
 *   An array of grants.
 * @param $bid
 *   The ID of the book for which the function returns the grant records.
 */
function _book_access_node_access_records_users(&$grants, $bid) {
  $result = db_query("SELECT * FROM {book_access_user} WHERE nid = %d", $bid);
  while ($grant = db_fetch_object($result)) {
    $grants[] = array(
      'realm' => 'book_access_user',
      'gid' => $grant->uid,
      'grant_view' => $grant->grant_view,
      'grant_update' => $grant->grant_update,
      'grant_delete' => $grant->grant_delete,
      'priority' => BOOK_ACCESS_GRANT_PRIORITY,
    );
  }
}

/**
 * Restricts the options available to who moves book pages between books.
 *
 * We don't want users to be able to add child pages to pages they do not
 * have update grants for; therefore, we remove select options which point
 * to book pages user does not have that grant.
 *
 * @param $options
 *   The options array used from book_outline_form() and the book edit form for
 *   the list of books to which the page can be moved to.
 *
 * @see book_access_form_alter()
 */
function _book_access_options_restrict(&$options) {
  if (user_access('administer nodes')) {
    return;
  }
  $permitted_bids = _book_access_user_books_list('update');
  if (isset($options)) {
    foreach ($options as $bid => $value) {
      if ($bid > 0 && !isset($permitted_bids[$bid])) {
        unset($options[$bid]);
      }
    }
  }
}
function _book_access_user_books_list($grant) {
  global $user;
  $permitted_bids = array();
  $roles = array_keys($user->roles);
  $query = db_query("SELECT nid FROM {book_access_role} WHERE rid IN (" . db_placeholders($roles) . ") AND grant_{$grant} > 0", $roles);
  while ($result = db_fetch_object($query)) {
    $permitted_bids[$result->nid] = $result->nid;
  }
  $query = db_query("SELECT nid FROM {book_access_user} WHERE uid = %d AND grant_{$grant} > 0", $user->uid);
  while ($result = db_fetch_object($query)) {
    $permitted_bids[$result->nid] = $result->nid;
  }
  return $permitted_bids;
}

Functions

Namesort descending Description
book_access_edit_form_submit Form submission callback for node_form(), and book_outline_form().
book_access_form_alter Implements hook_form_alter().
book_access_form_book_admin_settings_alter Implements hook_form_FORM_ID_alter() for book_admin_settings().
book_access_grant_check Checks if a user has access to the book passed as argument.
book_access_help Implements hook_help().
book_access_link_alter Implements hook_link_alter().
book_access_menu Implements hook_menu().
book_access_menu_alter Implements hook_menu_alter().
book_access_nodeapi Implements hook_nodeapi().
book_access_node_access_explain Implements hook_node_access_explain().
book_access_node_access_records Implements hook_node_access_records().
book_access_node_grants Implements hook_node_grants().
book_access_outline_access Determines if the outline tab is accessible.
book_access_outline_form_submit Form submission callback for book_outline_form().
book_access_outline_remove_access Determines if the user can remove nodes from the outline.
book_access_perm Implements hook_perm().
book_access_permissions_form_access Determines when the book access tab can be shown in the node edit page.
book_access_theme Implements hook_theme().
book_access_user Implements hook_user().
_book_access_build_node_grants Rebuilds the book page grants for the node passed as argument.
_book_access_node_access_records_roles Gets the list of grant records assigned to user roles for a book.
_book_access_node_access_records_users Gets the list of grant records assigned to users for a book.
_book_access_options_restrict Restricts the options available to who moves book pages between books.
_book_access_user_books_list

Constants

Namesort descending Description
BOOK_ACCESS_GRANT_PRIORITY @file