You are here

access.module in Access Control Kit 7

The access control kit module.

File

access.module
View source
<?php

/**
 * @file
 * The access control kit module.
 */

/**
 * Implements hook_entity_info().
 */
function access_entity_info() {

  // Access grants.
  $types['access_grant'] = array(
    'label' => t('Access grant'),
    'controller class' => 'AccessGrantEntityController',
    'base table' => 'access_grant',
    'uri callback' => 'access_grant_uri',
    'label callback' => 'access_grant_label',
    'fieldable' => TRUE,
    'entity keys' => array(
      'id' => 'gid',
      'bundle' => 'scheme',
    ),
    'bundle keys' => array(
      'bundle' => 'machine_name',
    ),
    'bundles' => array(),
    'view modes' => array(
      'full' => array(
        'label' => t('Access grant page'),
        'custom settings' => FALSE,
      ),
    ),
  );

  // Bundles are access schemes; the access realm field in the bundle determines
  // which objects are accessible to a user via an access grant.
  foreach (access_scheme_names() as $machine_name => $name) {
    $types['access_grant']['bundles'][$machine_name] = array(
      'label' => $name,
      'admin' => array(
        'path' => 'admin/structure/access/%access_scheme_machine_name',
        'real path' => 'admin/structure/access/' . str_replace('_', '-', $machine_name),
        'bundle argument' => 3,
        'access arguments' => array(
          'administer access schemes',
        ),
      ),
    );
  }

  // Access schemes.
  $types['access_scheme'] = array(
    'label' => t('Access scheme'),
    'controller class' => 'AccessSchemeEntityController',
    'base table' => 'access_scheme',
    'entity keys' => array(
      'id' => 'sid',
      'label' => 'name',
    ),
    'fieldable' => FALSE,
  );
  return $types;
}

/**
 * Entity URI callback for an access grant.
 */
function access_grant_uri($grant) {
  return array(
    'path' => 'admin/access/grant/' . $grant->gid,
  );
}

/**
 * Entity label callback for an access grant.
 *
 * @see callback_entity_info_label()
 */
function access_grant_label($grant) {

  // Load the primary descriptors of the grant.
  $account = user_load($grant->uid);
  $role = user_role_load($grant->rid);

  // Dereference the descriptors.
  $username = empty($account) ? $grant->uid : format_username($account);
  $rolename = empty($role) ? $grant->rid : $role->name;

  // We use !token instead of @token because an entity label callback is
  // supposed to return an unsanitized string.
  return t("!user's access as !role", array(
    '!user' => $username,
    '!role' => $rolename,
  ));
}

/**
 * Loads an access grant by ID.
 *
 * @param int $gid
 *   The grant ID.
 * @param bool $reset
 *   (optional) Whether to reset the internal cache.  Defaults to FALSE.
 *
 * @return object|false
 *   An access grant, or FALSE if the grant is not found.
 */
function access_grant_load($gid, $reset = FALSE) {
  $grants = access_grant_load_multiple(array(
    $gid,
  ), $reset);
  return reset($grants);
}

/**
 * Loads all access grants that match a set of conditions.
 *
 * @param array $conditions
 *   An array of search conditions. Valid conditions are:
 *   - 'scheme': a scheme machine name.
 *   - 'uid': a user ID.
 *   - 'rid': a role ID.
 *   - 'realms': an array of realm field values. Ignored if 'scheme' is omitted.
 * @param bool $reset
 *   (optional) Whether to reset the internal cache.  Defaults to FALSE.
 *
 * @return array
 *   An array of access grants that match the given conditions.
 */
function access_grant_load_by_condition($conditions = array(), $reset = FALSE) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'access_grant');
  if (isset($conditions['scheme'])) {
    $query
      ->entityCondition('bundle', $conditions['scheme']);
    if (isset($conditions['realms'])) {

      // Make sure that the referenced scheme actually exists.
      $scheme = access_scheme_machine_name_load($conditions['scheme']);
      if ($scheme) {
        $query
          ->fieldCondition($scheme->realm_field['field_name'], 'value', $conditions['realms']);
      }
      else {
        return array();
      }
    }
  }
  if (isset($conditions['uid'])) {
    $query
      ->propertyCondition('uid', $conditions['uid']);
  }
  if (isset($conditions['rid'])) {
    $query
      ->propertyCondition('rid', $conditions['rid']);
  }
  $result = $query
    ->execute();
  if (empty($result)) {
    return array();
  }
  $gids = array_keys($result['access_grant']);
  return access_grant_load_multiple($gids, $reset);
}

/**
 * Loads multiple access grants by ID.
 *
 * @param array|false $gids
 *   An array of grant IDs, or FALSE to load all grants.
 * @param bool $reset
 *   (optional) Whether to reset the internal cache.  Defaults to FALSE.
 *
 * @return array
 *   An array of access grants indexed by their IDs. When no results are found,
 *   returns an empty array.
 *
 * @see entity_load()
 */
function access_grant_load_multiple($gids = FALSE, $reset = FALSE) {
  return entity_load('access_grant', $gids, array(), $reset);
}

/**
 * Saves an access grant.
 *
 * @param object $grant
 *   An access grant.
 *
 * @return int
 *   Status constant indicating whether the grant was inserted (SAVED_NEW) or
 *   updated (SAVED_UPDATED). When inserting a new grant, $grant->gid will
 *   contain the ID of the newly created grant.
 *
 * @see AccessGrantEntityController::save()
 */
function access_grant_save($grant) {
  return entity_get_controller('access_grant')
    ->save($grant);
}

/**
 * Deletes an access grant.
 *
 * @param int $gid
 *   The grant ID.
 *
 * @return int
 *   Status constant indicating deletion.
 *
 * @see AccessGrantEntityController::delete()
 */
function access_grant_delete($gid) {
  $controller = entity_get_controller('access_grant');
  $status = $controller
    ->delete($gid);
  $controller
    ->resetCache();
  return $status;
}

/**
 * Constructs an array for drupal_render() from an array of access grants.
 *
 * @param array $grants
 *   An array of grants, as returned by access_grant_load_multiple().
 * @param string $view_mode
 *   (optional) The view mode. Defaults to 'full'.
 * @param int $weight
 *   (optional) An integer representing the weight of the first grant in the
 *   list. Defaults to 0.
 * @param string|null $langcode
 *   (optional) A language code to use for rendering. Defaults to NULL which is
 *   the global content language of the current request.
 *
 * @return array
 *   An array in the format expected by drupal_render().
 *
 * @see drupal_render()
 */
function access_grant_view_multiple($grants, $view_mode = 'full', $weight = 0, $langcode = NULL) {
  field_attach_prepare_view('access_grant', $grants, $view_mode, $langcode);
  entity_prepare_view('access_grant', $grants, $langcode);
  $build = array();
  foreach ($grants as $grant) {
    $build['grants'][$grant->gid] = access_grant_view($grant, $view_mode, $langcode);
    $build['grants'][$grant->gid]['#weight'] = $weight;
    $weight++;
  }
  $build['grants']['#sorted'] = TRUE;
  return $build;
}

/**
 * Generates a renderable array for an access grant.
 *
 * @param object $grant
 *   An access grant.
 * @param string $view_mode
 *   (optional) The view mode. Defaults to 'full'.
 * @param string|null $langcode
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
 *
 * @return array
 *   An array in the format expected by drupal_render().
 *
 * @see drupal_render()
 */
function access_grant_view($grant, $view_mode = 'full', $langcode = NULL) {
  if (!isset($langcode)) {
    $langcode = $GLOBALS['language_content']->language;
  }

  // Retrieve all fields and attach to $grant->content.
  access_grant_build_content($grant, $view_mode, $langcode);
  $build = $grant->content;

  // We don't need it twice.
  unset($grant->content);
  $build += array(
    '#theme' => 'access_grant',
    '#access_grant' => $grant,
    '#view_mode' => $view_mode,
    '#language' => $langcode,
  );
  $type = 'access_grant';
  drupal_alter(array(
    'access_grant_view',
    'entity_view',
  ), $build, $type);
  return $build;
}

/**
 * Builds a structured array representing the access grant in $grant->content.
 *
 * @param object $grant
 *   An access grant.
 * @param string $view_mode
 *   (optional) The view mode. Defaults to 'full'.
 * @param string|null $langcode
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
 */
function access_grant_build_content($grant, $view_mode = 'full', $langcode = NULL) {
  if (!isset($langcode)) {
    $langcode = $GLOBALS['language_content']->language;
  }

  // Remove previously built content, if any exists.
  $grant->content = array();

  // Build fields content. An internal flag prevents this from happening twice,
  // such as when called through access_grant_view_multiple().
  field_attach_prepare_view('access_grant', array(
    $grant->gid => $grant,
  ), $view_mode, $langcode);
  entity_prepare_view('access_grant', array(
    $grant->gid => $grant,
  ), $langcode);
  $grant->content += field_attach_view('access_grant', $grant, $view_mode, $langcode);
  module_invoke_all('access_grant_view', $grant, $view_mode, $langcode);
  module_invoke_all('entity_view', $grant, 'access_grant', $view_mode, $langcode);
}

/**
 * Menu title callback for adding a grant within a scheme.
 */
function access_grant_add_page_title($scheme) {
  return t('Grant access to @scheme', array(
    '@scheme' => $scheme->name,
  ));
}

/**
 * AJAX callback for the access grant form.
 */
function access_grant_form_ajax($form, $form_state) {
  return $form['role'];
}

/**
 * Returns a list of all defined access scheme names.
 *
 * @return array
 *   An array of access scheme names, keyed by machine name.
 */
function access_scheme_names() {
  $names =& drupal_static(__FUNCTION__);
  if (!isset($names)) {
    $names = db_query('SELECT machine_name, name FROM {access_scheme} ORDER BY name')
      ->fetchAllKeyed();
  }
  return $names;
}

/**
 * Loads an access scheme by ID.
 *
 * @param int $sid
 *   The scheme ID.
 * @param bool $reset
 *   (optional) Whether to reset the internal cache.  Defaults to FALSE.
 *
 * @return object|false
 *   An access scheme, or FALSE if the scheme is not found.
 */
function access_scheme_load($sid, $reset = FALSE) {
  $schemes = access_scheme_load_multiple(array(
    $sid,
  ), $reset);
  return reset($schemes);
}

/**
 * Loads an access scheme by its URL-friendly machine name.
 *
 * @param string $machine_name
 *   The machine-readable name of a scheme, where '_' may be replaced with '-'.
 * @param bool $reset
 *   (optional) Whether to reset the internal cache.  Defaults to FALSE.
 *
 * @return object|false
 *   An access scheme, or FALSE if the scheme is not found.
 */
function access_scheme_machine_name_load($machine_name, $reset = FALSE) {
  $machine_name = str_replace('-', '_', $machine_name);
  $map =& drupal_static(__FUNCTION__, array());
  if (!isset($map[$machine_name]) || $reset) {
    $map[$machine_name] = db_query('SELECT sid FROM {access_scheme} WHERE machine_name = :machine_name', array(
      ':machine_name' => $machine_name,
    ))
      ->fetchField();
  }

  // If the given machine name does not map to any known scheme ID, abort.
  if (empty($map[$machine_name])) {
    return FALSE;
  }
  $schemes = access_scheme_load_multiple(array(
    $map[$machine_name],
  ), $reset);
  return reset($schemes);
}

/**
 * Loads multiple access schemes by ID.
 *
 * @param array $sids
 *   An array of scheme IDs, or FALSE to load all schemes.
 * @param bool $reset
 *   (optional) Whether to reset the internal cache.  Defaults to FALSE.
 *
 * @return array
 *   An array of access schemes indexed by their IDs. When no results are found,
 *   returns an empty array.
 *
 * @see entity_load()
 */
function access_scheme_load_multiple($sids = FALSE, $reset = FALSE) {
  return entity_load('access_scheme', $sids, array(), $reset);
}

/**
 * Saves an access scheme.
 *
 * @param object $scheme
 *   An access scheme.
 *
 * @return int
 *   Status constant indicating whether the scheme was inserted (SAVED_NEW) or
 *   updated (SAVED_UPDATED). When inserting a new scheme, $scheme->sid will
 *   contain the ID of the newly created scheme.
 *
 * @see AccessSchemeEntityController::save()
 */
function access_scheme_save($scheme) {
  return entity_get_controller('access_scheme')
    ->save($scheme);
}

/**
 * Deletes an access scheme.
 *
 * @param int $sid
 *   The scheme ID.
 *
 * @return int
 *   Status constant indicating deletion.
 *
 * @see AccessSchemeEntityController::delete()
 */
function access_scheme_delete($sid) {
  return entity_get_controller('access_scheme')
    ->delete($sid);
}

/**
 * Clear all static cache variables for access schemes.
 *
 * @param array $ids
 *   An array of ids to reset in the entity controller cache.
 */
function access_scheme_static_reset($ids = NULL) {
  drupal_static_reset('access_scheme_names');
  drupal_static_reset('access_scheme_machine_name_load');
  entity_get_controller('access_scheme')
    ->resetCache($ids);
}

/**
 * Returns information on available access scheme types.
 *
 * @param string $type
 *   (optional) A scheme type.
 *
 * @return array|false
 *   The information array for the requested $type, or FALSE if not found.
 *   If $type is omitted, returns information for all available types in an
 *   array indexed by type. Type information is returned as an associative array
 *   with the following keys:
 *   - label: The human-readable name of the scheme type.
 *   - data_type: The data type of the realm values. Valid data types are
 *     'boolean', 'integer', 'float' and 'text'.
 *   - description: (optional) A translated string describing the scheme type.
 *   - realms callback: The name of the function that provides the realm list.
 *   - settings callback: (optional) The name of the function that provides the
 *     scheme type's settings form.
 *   - type: The scheme type.
 *   - module: The module that provides the scheme type.
 *   - include file: (optional) The full pathname of a file that should be
 *     included before executing the callback functions.
 *
 * @see hook_access_scheme_info()
 * @see hook_access_scheme_info_alter()
 */
function access_scheme_info($type = NULL) {
  $info =& drupal_static(__FUNCTION__, array());
  if (empty($info)) {
    $cache = cache_get('access_scheme_info');
    if ($cache) {
      $info = $cache->data;
    }
    else {
      foreach (module_implements('access_scheme_info') as $module) {
        $module_info = module_invoke($module, 'access_scheme_info');
        if ($module_info) {
          foreach ($module_info as $type_name => $type_info) {

            // Merge in default values.
            $type_info += array(
              'label' => '',
              'data_type' => '',
              'description' => '',
              'realms callback' => '',
              'settings callback' => '',
              'file' => '',
              'file path' => '',
            );

            // Set inferred values.
            $type_info['type'] = $type_name;
            $type_info['module'] = $module;
            $info[$type_name] = $type_info;
          }
        }
      }
      drupal_alter('access_scheme_info', $info);

      // Calculate the include file paths for the callbacks, if needed.
      foreach ($info as $type_name => $type_info) {
        if (!empty($type_info['file'])) {
          $file_path = empty($type_info['file path']) ? drupal_get_path('module', $type_info['module']) : $type_info['file path'];
          $info[$type_name]['include file'] = $file_path . '/' . $type_info['file'];
        }
        else {
          $info[$type_name]['include file'] = '';
        }
        unset($info[$type_name]['file']);
        unset($info[$type_name]['file path']);
      }
      cache_set('access_scheme_info', $info);
    }
  }
  if (isset($type)) {
    return isset($info[$type]) ? $info[$type] : FALSE;
  }
  return $info;
}

/**
 * Loads the info for a scheme type from a URL-friendly argument.
 *
 * @param string $type
 *   A scheme type string, where '_' may be replaced with '-'.
 *
 * @return array|false
 *   The information array for the requested $type, or FALSE if not found.
 */
function access_scheme_type_load($type) {
  return access_scheme_info(str_replace('-', '_', $type));
}

/**
 * Allowed values callback for realm fields.
 *
 * This function is named in $field['settings']['allowed_values_function'] on
 * scheme realm fields. It is called by list_allowed_values() to generate the
 * options for the list field.
 *
 * The strings are not safe for output. Keys and values of the array should be
 * sanitized through field_filter_xss() before being displayed.
 *
 * @param array $field
 *   The field definition.
 * @param array|null $instance
 *   A field instance array. May be NULL.
 * @param array|null $entity_type
 *   The type of entity; for example, 'node' or 'user'. May be NULL.
 * @param object|null $entity
 *   The entity object. May be NULL.
 * @param true &$cacheable
 *   Boolean passed by reference to indicate whether the value list returned by
 *   this function should be cached by list_allowed_values(). Passed in as TRUE;
 *   if set to FALSE, the allowed values list will not be statically cached.
 *
 * @return array
 *   The array of allowed values. Keys of the array are the raw stored values
 *   (number or text), values of the array are the display labels.
 */
function _access_field_allowed_values($field, $instance, $entity_type, $entity, &$cacheable) {
  $values = array();
  if (!empty($field['bundles']['access_grant'])) {

    // A realm field should only be attached to one scheme, so we can just take
    // the first value in the bundle list.
    $machine_name = reset($field['bundles']['access_grant']);
    $scheme = access_scheme_machine_name_load($machine_name);
    if ($scheme) {
      $values = $scheme->realms;
    }
  }
  return $values;
}

/**
 * Implements hook_field_extra_fields().
 */
function access_field_extra_fields() {
  $extra = array();
  $fields = array(
    'user' => array(
      'label' => t('User'),
      'description' => t('Access control kit user reference'),
      'weight' => -5,
    ),
    'role' => array(
      'label' => t('Role'),
      'description' => t('Access control kit role reference'),
      'weight' => -4,
    ),
  );
  foreach (array_keys(access_scheme_names()) as $machine_name) {
    $extra['access_grant'][$machine_name] = array(
      'form' => $fields,
      'display' => $fields,
    );
  }
  return $extra;
}

/**
 * Returns information on available object access handlers.
 *
 * @param string $handler
 *   (optional) The name of a handler class.
 *
 * @return array|false
 *   The information array for the requested $handler, or FALSE if not found.
 *   If $handler is omitted, returns information for all available handlers in
 *   an array indexed by handler name. Handler information is returned as an
 *   associative array with the following keys:
 *   - label: The human-readable name of this handler.
 *   - scheme types: An array listing the scheme types that this handler
 *     supports, as defined in hook_access_scheme_info().
 *   - object types: An array listing the object types that this handler
 *     supports, as defined in hook_access_info(). A value of 'fieldable entity'
 *     indicates that the handler supports all object types that are fieldable
 *     entities, as defined by hook_entity_info().
 *   - class: The name of the handler class.
 *   - module: The module that provides the handler class.
 *
 * @see hook_access_handler_info()
 * @see hook_access_handler_info_alter()
 */
function access_handler_info($handler = NULL) {
  $info =& drupal_static(__FUNCTION__, array());
  if (empty($info)) {
    $cache = cache_get('access_handler_info');
    if ($cache) {
      $info = $cache->data;
    }
    else {
      foreach (module_implements('access_handler_info') as $module) {
        $module_info = module_invoke($module, 'access_handler_info');
        if ($module_info) {
          foreach ($module_info as $handler_name => $handler_info) {

            // Merge in default values.
            $handler_info += array(
              'label' => '',
              'scheme types' => array(),
              'object types' => array(),
            );

            // Set inferred values.
            $handler_info['class'] = $handler_name;
            $handler_info['module'] = $module;
            $info[$handler_name] = $handler_info;
          }
        }
      }
      drupal_alter('access_handler_info', $info);
      cache_set('access_handler_info', $info);
    }
  }
  if (isset($handler)) {
    return isset($info[$handler]) ? $info[$handler] : FALSE;
  }
  return $info;
}

/**
 * Returns information on available access-controlled object types.
 *
 * @param string $type
 *   (optional) An object type, such as 'node' or 'menu_link'.
 *
 * @return array|false
 *   The information array for the requested $type, or FALSE if not found.
 *   If $type is omitted, returns information for all available object types in
 *   an array indexed by object type. Type information is returned as an
 *   associative array with the following keys:
 *   - label: The human-readable name of this object type (e.g., "Content",
 *     "Menu link").
 *   - type: The machine name of this object type (e.g., node, menu_link).
 *   - module: The module that provides ACK-compatibility for the object type.
 *   - fieldable: Boolean indicating whether or not this object type is a
 *     fieldable entity, as defined by hook_entity_info().
 *   - handlers: An array listing the access handlers that support this object
 *     type, as defined in hook_access_handler_info().
 *
 * @see hook_access_info()
 * @see hook_access_info_alter()
 */
function access_info($type = NULL) {
  $info =& drupal_static(__FUNCTION__, array());
  if (empty($info)) {
    $cache = cache_get('access_info');
    if ($cache) {
      $info = $cache->data;
    }
    else {
      foreach (module_implements('access_info') as $module) {
        $module_info = module_invoke($module, 'access_info');
        if ($module_info) {
          foreach ($module_info as $object_type => $object_info) {

            // Merge in default values.
            $object_info += array(
              'label' => '',
            );

            // Set inferred values.
            $object_info['type'] = $object_type;
            $object_info['module'] = $module;
            $object_info['handlers'] = array();

            // Check whether the object is a fieldable entity.
            $entity_info = entity_get_info($object_type);
            $object_info['fieldable'] = empty($entity_info) ? FALSE : $entity_info['fieldable'];
            $info[$object_type] = $object_info;
          }
        }
      }
      drupal_alter('access_info', $info);
      cache_set('access_info', $info);
    }

    // Find handlers that support this object type. This information is already
    // cached in access_handler_info(), so we only include this in the static
    // variable, not in the cache_set() above.
    $handlers = access_handler_info();
    foreach ($info as $object_type => $object_info) {
      foreach ($handlers as $handler_name => $handler_info) {

        // If the object is a fieldable entity and the handler supports
        // fieldable entities, or if the handler explicitly supports this
        // object type, add the handler to the list.
        if ($object_info['fieldable'] && in_array('fieldable entity', $handler_info['object types']) || in_array($object_type, $handler_info['object types'])) {
          $info[$object_type]['handlers'][] = $handler_name;
        }
      }
    }
  }
  if (isset($type)) {
    return isset($info[$type]) ? $info[$type] : FALSE;
  }
  return $info;
}

/**
 * Clears cached ACK API information.
 */
function access_info_cache_clear() {
  cache_clear_all('access_scheme_info', 'cache');
  drupal_static_reset('access_scheme_info');
  cache_clear_all('access_handler_info', 'cache');
  drupal_static_reset('access_handler_info');
  cache_clear_all('access_info', 'cache');
  drupal_static_reset('access_info');
}

/**
 * Finds all access schemes that control access to a given object type.
 *
 * @param string $object_type
 *   An access-controlled object type name (e.g., node, menu_link), as defined
 *   by hook_access_info().
 * @param bool $names_only
 *   (optional) If set to TRUE, the returned array will contain only the names
 *   of the applicable access schemes, rather than fully loaded scheme entities.
 *   Defaults to FALSE.
 *
 * @return array
 *   An array containing all access schemes that have an object access handler
 *   attached for the given object type, keyed by scheme machine name.
 */
function access_object_schemes($object_type, $names_only = FALSE) {
  $object_schemes =& drupal_static(__FUNCTION__, array());

  // Get the machine names and IDs for all schemes that have a handler attached
  // for the given object type.
  if (!isset($object_schemes[$object_type])) {
    $object_schemes[$object_type] = db_query('SELECT s.machine_name, s.sid FROM {access_scheme} s INNER JOIN {access_handler} h ON s.machine_name = h.scheme WHERE h.object_type = :object_type', array(
      ':object_type' => $object_type,
    ))
      ->fetchAllKeyed();
  }

  // Return either the names or the objects for the applicable schemes.
  $scheme_list = array();
  if (!empty($object_schemes[$object_type])) {
    if ($names_only) {
      $scheme_list = array_intersect_key(access_scheme_names(), $object_schemes[$object_type]);
    }
    else {
      $schemes = access_scheme_load_multiple($object_schemes[$object_type]);
      foreach ($object_schemes[$object_type] as $machine_name => $sid) {
        $scheme_list[$machine_name] = $schemes[$sid];
      }
    }
  }
  return $scheme_list;
}

/**
 * Returns a list of an object's realm memberships.
 *
 * @param string $object_type
 *   The type of access-controlled object (e.g., node, menu_link).
 * @param mixed $object
 *   The access-controlled object.
 * @param array $schemes
 *   (optional) An array of access schemes, used to limit the list of returned
 *   realm memberships.  If omitted, the returned list will include realm
 *   memberships for all schemes that apply to the object type.
 *
 * @return array
 *   An array indexed by scheme machine name where each value is an array of
 *   realm values.  If the object is not a member of any realm in a scheme, the
 *   value returned for that scheme will be array().
 */
function access_object_realms($object_type, $object, $schemes = NULL) {
  if (!isset($schemes)) {
    $schemes = access_object_schemes($object_type);
  }
  $realms = array();
  foreach ($schemes as $scheme) {
    $realms[$scheme->machine_name] = isset($scheme->handlers[$object_type]) ? $scheme->handlers[$object_type]
      ->objectRealms($object_type, $object) : array();
  }
  return $realms;
}

/**
 * Returns a list of a user's realm-level role memberships.
 *
 * @param object $account
 *   (optional) The account to check. Defaults to the currently logged in user.
 *
 * @return array
 *   A nested array indexed first by role ID, then by scheme machine name, where
 *   each value is an array containing the user's assigned realms for that role
 *   in that scheme as realm_value => realm_label. Note that the list of roles
 *   is limited to those found in $scheme->roles for each scheme; non-ACK roles
 *   are excluded.
 */
function access_user_roles($account = NULL) {
  global $user;
  if (!isset($account)) {
    $account = $user;
  }

  // Cache the user's role list using the advanced drupal_static() pattern for
  // best performance on repeated access checks.
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['user_roles'] =& drupal_static(__FUNCTION__);
  }
  $user_roles =& $drupal_static_fast['user_roles'];

  // On cache miss, load all grants for the user and sort by role and scheme.
  if (!isset($user_roles[$account->uid])) {
    $grants = access_grant_load_by_condition(array(
      'uid' => $account->uid,
    ));
    $roles = array();
    foreach ($grants as $grant) {
      $scheme = access_scheme_machine_name_load($grant->scheme);

      // Only include roles that are in the current list of ACK-enabled roles
      // (in case the scheme settings have changed), and that are currently
      // associated with the user (in case another module revoked a role without
      // ACK being notified to clean up grants).
      if (isset($scheme->roles[$grant->rid]) && isset($account->roles[$grant->rid])) {
        $roles[$grant->rid][$grant->scheme] = $grant->realms;
      }
    }
    $user_roles[$account->uid] = $roles;
  }
  return $user_roles[$account->uid];
}

/**
 * Returns a list of realms in which the user has a given permission.
 *
 * @param string $string
 *   The permission string (e.g., edit any page).
 * @param object $account
 *   (optional) The account to check. Defaults to the currently logged in user.
 * @param array $schemes
 *   (optional) An array of access schemes, used to limit the list of returned
 *   realms.  If omitted, the returned list will include realms for all schemes.
 *
 * @return array
 *   An array indexed by scheme machine name where each value is an array of
 *   realm values.  If the user does not have the permission for any realm in a
 *   scheme, the value returned for that scheme will be array().
 */
function access_user_permission_realms($string, $account = NULL, $schemes = NULL) {
  global $user;
  if (!isset($account)) {
    $account = $user;
  }
  if (!isset($schemes)) {
    $schemes = access_scheme_load_multiple();
  }

  // Get the user's realm-level role memberships.
  $roles = access_user_roles($account);

  // Filter out any roles that do not include the permission we're looking for.
  foreach (user_role_permissions($roles) as $rid => $permissions) {
    if (empty($permissions[$string])) {
      unset($roles[$rid]);
    }
  }

  // Find the accessible realms for each scheme.
  $realms = array();
  foreach ($schemes as $scheme) {
    $scheme_realms = array();
    foreach ($roles as $access) {
      if (!empty($access[$scheme->machine_name])) {
        $scheme_realms += $access[$scheme->machine_name];
      }
    }
    $realms[$scheme->machine_name] = array_keys($scheme_realms);
  }
  return $realms;
}

/**
 * Determines whether a user has a permission on an object via an access grant.
 *
 * @param string $string
 *   The permission string (e.g., edit any page).
 * @param string $object_type
 *   The type of access-controlled object (e.g., node, menu_link).
 * @param mixed $object
 *   The access-controlled object.
 * @param object $account
 *   (optional) The account to check. Defaults to the currently logged in user.
 * @param array $schemes
 *   (optional) An array of schemes in which to check access on the object. If
 *   omitted, access will be checked in all available schemes that apply to the
 *   object type.
 *
 * @return bool
 *   Boolean TRUE if the user has the requested permission on the object in any
 *   of the tested schemes.
 */
function access_user_object_access($string, $object_type, $object, $account = NULL, $schemes = NULL) {
  global $user;
  if (!isset($account)) {
    $account = $user;
  }

  // Sanity check to make sure that the user has the permission at all.
  if (user_access($string, $account)) {
    if (!isset($schemes)) {
      $schemes = access_object_schemes($object_type);
    }

    // Get the list of realms wherein the user has the permission.
    $user_realms = access_user_permission_realms($string, $account, $schemes);

    // Get the object's realm memberships.
    $object_realms = access_object_realms($object_type, $object, $schemes);

    // If the user has the permission in any realm of which the object is also a
    // member, return TRUE to allow access.
    foreach ($schemes as $scheme) {
      $matches = array_intersect($user_realms[$scheme->machine_name], $object_realms[$scheme->machine_name]);
      if (!empty($matches)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Implements hook_permission().
 */
function access_permission() {
  $perms['administer access schemes'] = array(
    'title' => t('Administer access schemes'),
    'description' => t('Define the means by which objects in the site are divided into access realms.'),
    'restrict access' => TRUE,
  );
  $perms['administer access grants'] = array(
    'title' => t('Administer access grants'),
    'description' => t('Assign roles to users within access realms.'),
    'restrict access' => TRUE,
  );
  return $perms;
}

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

  // Manage access grants.
  $items['admin/access'] = array(
    'title' => 'Access',
    'description' => 'Manage access grants.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'access_overview_grants',
    ),
    'access arguments' => array(
      'administer access grants',
    ),
    // -2 places it between "People" and "Modules" on the default admin menu.
    'weight' => -2,
    'file' => 'access_grants.admin.inc',
  );
  $items['admin/access/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/access/add'] = array(
    'title' => 'Add access grant',
    'page callback' => 'access_grant_add_list',
    'access arguments' => array(
      'administer access grants',
    ),
    'type' => MENU_LOCAL_ACTION,
    'file' => 'access_grants.admin.inc',
  );
  $items['admin/access/add/%access_scheme_machine_name'] = array(
    'title' => 'Add access grant for scheme',
    'title callback' => 'access_grant_add_page_title',
    'title arguments' => array(
      3,
    ),
    'page callback' => 'access_grant_add',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'administer access grants',
    ),
    'file' => 'access_grants.admin.inc',
  );
  $items['admin/access/grant/%access_grant'] = array(
    'title callback' => 'entity_label',
    'title arguments' => array(
      'access_grant',
      3,
    ),
    'page callback' => 'access_grant_page',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'administer access grants',
    ),
    'file' => 'access.pages.inc',
  );
  $items['admin/access/grant/%access_grant/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/access/grant/%access_grant/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'access_grant_edit',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'administer access grants',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'access_grants.admin.inc',
  );
  $items['admin/access/grant/%access_grant/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'access_grant_delete_confirm',
      3,
    ),
    'access arguments' => array(
      'administer access grants',
    ),
    'file' => 'access_grants.admin.inc',
  );

  // Manage access schemes.
  $items['admin/structure/access'] = array(
    'title' => 'Access schemes',
    'description' => 'Define access control schemes.',
    'page callback' => 'access_overview_schemes',
    'access arguments' => array(
      'administer access schemes',
    ),
    'file' => 'access_schemes.admin.inc',
  );
  $items['admin/structure/access/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/structure/access/add'] = array(
    'title' => 'Add access scheme',
    'page callback' => 'access_scheme_add_list',
    'access arguments' => array(
      'administer access schemes',
    ),
    'type' => MENU_LOCAL_ACTION,
    'file' => 'access_schemes.admin.inc',
  );
  $items['admin/structure/access/add/%access_scheme_type'] = array(
    'title' => 'Add access scheme',
    'page callback' => 'access_scheme_add',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'administer access schemes',
    ),
    'file' => 'access_schemes.admin.inc',
  );
  $items['admin/structure/access/%access_scheme_machine_name'] = array(
    'title callback' => 'entity_label',
    'title arguments' => array(
      'access_scheme',
      3,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'access_scheme_form',
      3,
    ),
    'access arguments' => array(
      'administer access schemes',
    ),
    'file' => 'access_schemes.admin.inc',
  );
  $items['admin/structure/access/%access_scheme_machine_name/edit'] = array(
    'title' => 'Edit',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/structure/access/%access_scheme_machine_name/delete'] = array(
    'title' => 'Delete',
    'page arguments' => array(
      'access_scheme_delete_confirm',
      3,
    ),
    'access arguments' => array(
      'administer access schemes',
    ),
    'file' => 'access_schemes.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function access_theme() {
  return array(
    'access_overview_scheme_name' => array(
      'variables' => array(
        'scheme' => NULL,
      ),
      'file' => 'access_schemes.admin.inc',
    ),
    'access_grant' => array(
      'render element' => 'elements',
      'template' => 'access-grant',
      'file' => 'access.pages.inc',
    ),
  );
}

/**
 * Implements hook_modules_uninstalled().
 */
function access_modules_uninstalled($modules) {
  foreach ($modules as $module) {
    db_delete('access_handler')
      ->condition('module', $module)
      ->execute();
  }
}

/**
 * Implements hook_field_create_field().
 */
function access_field_create_field($field) {
  access_info_cache_clear();
}

/**
 * Implements hook_field_update_field().
 */
function access_field_update_field($field, $prior_field, $has_data) {
  access_info_cache_clear();
}

/**
 * Implements hook_field_delete_field().
 */
function access_field_delete_field($field) {
  access_info_cache_clear();
}

/**
 * Implements hook_taxonomy_vocabulary_insert().
 */
function access_taxonomy_vocabulary_insert($vocabulary) {
  access_info_cache_clear();
}

/**
 * Implements hook_taxonomy_vocabulary_update().
 */
function access_taxonomy_vocabulary_update($vocabulary) {
  access_info_cache_clear();
}

/**
 * Implements hook_taxonomy_vocabulary_delete().
 */
function access_taxonomy_vocabulary_delete($vocabulary) {
  access_info_cache_clear();
}

/**
 * Implements hook_hook_info().
 */
function access_hook_info() {
  $group = array(
    'group' => 'access',
  );
  $list = array(
    'access_info',
    'access_handler_info',
    'access_scheme_info',
    'access_scheme_load',
    'access_scheme_presave',
    'access_scheme_insert',
    'access_scheme_update',
    'access_scheme_delete',
    'access_scheme_views_data_alter',
    'access_grant_presave',
    'access_grant_insert',
    'access_grant_update',
    'access_grant_delete',
    'access_grant_view',
  );
  $hooks = array();
  foreach ($list as $hook) {
    $hooks[$hook] = $group;
  }
  return $hooks;
}

/**
 * Implements hook_help().
 */
function access_help($path, $arg) {
  $output = '';
  switch ($path) {

    // The main help page.
    case 'admin/help#access':
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Access Control Kit module allows you to give users access to manage parts of your site. To control access, you define <em>access schemes</em> to divide the site into <em>access realms</em>, and then assign ACK-enabled roles to users within those realms.') . '</p>';
      $output .= '<h3>' . t('How to use') . '</h3>';
      $output .= '<h4>' . t('Creating access schemes') . '</h4>';
      $output .= '<p>' . t('Users with sufficient <a href="@perm">permissions</a> can create access schemes through the <a href="@admin">access schemes page</a>. The Access Control Kit module includes the ability to create schemes based on taxonomy vocabularies, list fields, user accounts, and boolean values. Other modules may define additional scheme types through the ACK API. The type of scheme you create determines the access realms for your site. For example, in a taxonomy-based scheme, the vocabulary terms are the realms. In a list-based scheme, the allowed values of the list field are the realms.', array(
        '@perm' => url('admin/people/permissions', array(
          'fragment' => 'module-access',
        )),
        '@admin' => url('admin/structure/access'),
      )) . '</p>';
      $output .= '<h4>' . t('Assigning realms to objects') . '</h4>';
      $output .= '<p>' . t("To control access to an object in your site, the scheme needs to be configured to relate its realms to the object. For example, a taxonomy-based scheme might use the value of a term reference field; objects tagged with a term become members of that term's realm. Access Control Kit includes two modules for managing objects:") . '</p>';
      $output .= '<dl><dt>ACK Node</dt><dd>Can manage access to content through field values (for taxonomy- and list-based schemes), the "sticky" property (for boolean schemes), or the content author (for user-based schemes).</dd><dt>ACK Menu</dt><dd>Can manage access to menus by assigning a link and its sublinks to a realm in a taxonomy or list (integer) scheme.</dd></dl>';
      $output .= '<p>' . t('Other modules may provide support for additional object types through the ACK API.') . '</p>';
      $output .= '<h4>' . t('Creating ACK-enabled roles') . '</h4>';
      $output .= '<p>' . t('Modules that manage object types also provide realm-aware permissions for those objects. For example, the ACK Node module provides permissions to create, edit own, edit any, delete own, and delete any content in assigned realms; users with these permissions will only be able to affect content in the realms that have been assigned to them. To create an ACK-enabled role, assign these permissions to the role on the <a href="@perm">permissions page</a> as normal, then edit your access scheme and enable the role.', array(
        '@perm' => url('admin/people/permissions'),
      )) . '</p>';
      $output .= '<h4>' . t('Assigning users to realms') . '</h4>';
      $output .= '<p>' . t('Users with sufficient <a href="@perm">permissions</a> can create <em>access grants</em> through the <a href="@admin">access page</a>. Access grants allow users to exercise their ACK-enabled roles on the member objects of a realm.', array(
        '@perm' => url('admin/people/permissions', array(
          'fragment' => 'module-access',
        )),
        '@admin' => url('admin/access'),
      )) . '</p>';
      $output .= '<h3>' . t('Extending the Access Control Kit module') . '</h3>';
      $output .= '<p>' . t("The file access.api.php (in the access module directory) contains information for developers interested in extending the capabilities of ACK or integrating ACK with their own modules. In addition, the ACK Node and ACK Menu modules serve as good examples of how to implement the API and override Drupal's regular access controls.") . '</p>';
      break;

    // The access scheme overview page.
    case 'admin/structure/access':
      $output .= '<p>' . t('Access schemes group together the components of your site (such as content and menu links) for the purpose of giving users access to manage them. Each grouping within the scheme is called an <em>access realm</em>.') . '</p>';
      $output .= '<p>' . t('An access scheme can use a taxonomy vocabulary, a list field, or some other source for its realm list. For example, in a vocabulary-based scheme, the terms in that vocabulary will become access realms. If the scheme is configured to manage content, then a user could be given access to only edit content tagged with a certain term.') . '</p>';
      break;
    default:
      $output = NULL;
  }
  return $output;
}

/**
 * Implements hook_views_api().
 */
function access_views_api() {
  return array(
    'api' => '3.0',
  );
}

/**
 * Implements hook_features_api().
 */
function access_features_api() {
  return array(
    'access_scheme' => array(
      'name' => t('Access schemes'),
      'default_hook' => 'access_default_schemes',
      'default_file' => FEATURES_DEFAULTS_INCLUDED,
      'feature_source' => TRUE,
      'file' => drupal_get_path('module', 'access') . '/access.features.inc',
    ),
  );
}

Functions

Namesort descending Description
access_entity_info Implements hook_entity_info().
access_features_api Implements hook_features_api().
access_field_create_field Implements hook_field_create_field().
access_field_delete_field Implements hook_field_delete_field().
access_field_extra_fields Implements hook_field_extra_fields().
access_field_update_field Implements hook_field_update_field().
access_grant_add_page_title Menu title callback for adding a grant within a scheme.
access_grant_build_content Builds a structured array representing the access grant in $grant->content.
access_grant_delete Deletes an access grant.
access_grant_form_ajax AJAX callback for the access grant form.
access_grant_label Entity label callback for an access grant.
access_grant_load Loads an access grant by ID.
access_grant_load_by_condition Loads all access grants that match a set of conditions.
access_grant_load_multiple Loads multiple access grants by ID.
access_grant_save Saves an access grant.
access_grant_uri Entity URI callback for an access grant.
access_grant_view Generates a renderable array for an access grant.
access_grant_view_multiple Constructs an array for drupal_render() from an array of access grants.
access_handler_info Returns information on available object access handlers.
access_help Implements hook_help().
access_hook_info Implements hook_hook_info().
access_info Returns information on available access-controlled object types.
access_info_cache_clear Clears cached ACK API information.
access_menu Implements hook_menu().
access_modules_uninstalled Implements hook_modules_uninstalled().
access_object_realms Returns a list of an object's realm memberships.
access_object_schemes Finds all access schemes that control access to a given object type.
access_permission Implements hook_permission().
access_scheme_delete Deletes an access scheme.
access_scheme_info Returns information on available access scheme types.
access_scheme_load Loads an access scheme by ID.
access_scheme_load_multiple Loads multiple access schemes by ID.
access_scheme_machine_name_load Loads an access scheme by its URL-friendly machine name.
access_scheme_names Returns a list of all defined access scheme names.
access_scheme_save Saves an access scheme.
access_scheme_static_reset Clear all static cache variables for access schemes.
access_scheme_type_load Loads the info for a scheme type from a URL-friendly argument.
access_taxonomy_vocabulary_delete Implements hook_taxonomy_vocabulary_delete().
access_taxonomy_vocabulary_insert Implements hook_taxonomy_vocabulary_insert().
access_taxonomy_vocabulary_update Implements hook_taxonomy_vocabulary_update().
access_theme Implements hook_theme().
access_user_object_access Determines whether a user has a permission on an object via an access grant.
access_user_permission_realms Returns a list of realms in which the user has a given permission.
access_user_roles Returns a list of a user's realm-level role memberships.
access_views_api Implements hook_views_api().
_access_field_allowed_values Allowed values callback for realm fields.