module_grants.module in Module Grants 6
Same filename and directory in other branches
Module to enable access control for unpublished content. Also makes sure that modules that operate on access grants behave in the expected way when enabled together.
File
module_grants.moduleView source
<?php
/**
* @file
*
* Module to enable access control for unpublished content. Also makes
* sure that modules that operate on access grants behave in the expected
* way when enabled together.
*/
/**
* Implementation of hook_perm().
*
* Revisioning permissions. Note that permissions to view, revert and delete
* revisions already exist in node.module.
*/
function module_grants_perm() {
return array(
'access content summary',
);
}
/**
* Implementation of hook_menu().
*
* Define new menu items.
* Existing menu items are modified through hook_menu_alter().
*/
function module_grants_menu() {
$items = array();
// Create a normal menu item in root Navigation menu
$items['content'] = array(
'title' => 'My content',
'page callback' => 'module_grants_editable_nodes',
'access arguments' => array(
'access content summary',
),
);
// Add two tabs on page defined above
$items['content/editable'] = array(
'title' => 'Editable',
'page callback' => 'module_grants_editable_nodes',
'access arguments' => array(
'access content summary',
),
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['content/viewable'] = array(
'title' => 'Viewable',
'page callback' => 'module_grants_viewable_nodes',
'access arguments' => array(
'access content summary',
),
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implementation of hook_menu_alter().
*
* Modify menu items defined in other modules (in particular the node module).
*/
function module_grants_menu_alter(&$items) {
// As module_grants_node_access() fixes the problem of grants not being
// checked when a node isn't published, all node access menu links are
// altered to use this function.
// For normal view/edit/delete operations module_grant_node_access() is
// called directly, for the revision-specific operations the function is
// called via mode_grants_node_revision_access().
$items['node/%node']['access callback'] = 'module_grants_node_access';
$items['node/%node']['access arguments'] = array(
'view',
1,
);
$items['node/%node/view']['access callback'] = 'module_grants_node_access';
$items['node/%node/view']['access arguments'] = array(
'view',
1,
);
$items['node/%node/edit']['access callback'] = 'module_grants_node_access';
$items['node/%node/delete']['access callback'] = 'module_grants_node_access';
$items['node/%node/revisions']['access callback'] = 'module_grants_node_revision_access';
$items['node/%node/revisions']['access arguments'] = array(
'view revisions',
1,
);
// Point /%node/revisions/%/view page to same callback as /%node/view for a
// consistent view of current and other revisions
$items['node/%node/revisions/%/view']['page callback'] = 'node_page_view';
// as used by /%node/view
$items['node/%node/revisions/%/view']['page arguments'] = array(
1,
);
$items['node/%node/revisions/%/view']['access callback'] = 'module_grants_node_revision_access';
$items['node/%node/revisions/%/view']['access arguments'] = array(
'view revisions',
1,
);
$items['node/%node/revisions/%/delete']['access callback'] = 'module_grants_node_revision_access';
$items['node/%node/revisions/%/delete']['access arguments'] = array(
'delete revisions',
1,
);
$items['node/%node/revisions/%/revert']['access callback'] = 'module_grants_node_revision_access';
$items['node/%node/revisions/%/revert']['access arguments'] = array(
'revert revisions',
1,
);
}
/**
* Menu options dealing with revisions have their revision-specific
* permission checked before being tested for the associated node-specific
* operation.
* Return a boolean indicating whether the current user has the requested
* permission AND access to the given node (regardless of its published
* status).
*
* @param $node
* Node object for which access right is requested
* @param $permission
* The requested permission, as a string eg 'delete revisions'
* @return
* TRUE when the current user has access to the supplied node
*/
function module_grants_node_revision_access($revision_permission, $node) {
// Map revision-permission to node access operation used in grants
switch ($revision_permission) {
case 'edit revisions':
$op = 'update';
break;
case 'delete revisions':
$op = 'delete';
break;
default:
$op = 'view';
break;
}
return user_access($revision_permission) && module_grants_node_access($op, $node);
}
/**
* Similar to node_access() in node.module but ANDs rather than ORs grants
* together on a per module base to create more natural behaviour.
* Also makes sure that published and unpublished content are treated
* in the same way, i.e. that grants are checked in either case.
*
* @param $op
* One of 'view', 'update' or 'delete'. 'create' isn't used.
* @param $node
* The node for which the supplied operation is checked
* @param $account
* user object, use NULL or omit for current user
* @return
* FALSE if the supplied operation isn't permitted on the node
*/
function module_grants_node_access($op, $node, $account = NULL) {
global $user;
if (!$node) {
return FALSE;
}
// If the node is in a restricted format, disallow editing.
if ($op == 'update' && !filter_access($node->format)) {
return FALSE;
}
// If no user object is supplied, the access check is for the current user.
if (empty($account)) {
$account = $user;
}
if (user_access('administer nodes', $account)) {
return TRUE;
}
if (!user_access('access content', $account)) {
return FALSE;
}
$module = node_get_types('module', $node);
if ($module == 'node') {
$module = 'node_content';
}
$access = module_invoke($module, 'access', $op, $node, $account);
if (!is_null($access)) {
//drupal_set_message("'$op' access=$access by $module: '$node->title'", 'warning');
return $access;
}
// If the module neither allows nor denies access, then find grants
// amongst modules that implement hook_node_grants()
$all_grants = grants_by_module($op, $account);
$base_sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid=0 OR nid=%d) AND ((gid=0 AND realm='all')";
if (count($all_grants) == 0) {
// no module implements hook_node_grants()
$sql = "{$base_sql}) AND grant_{$op} >=1";
$result = db_result(db_query($sql, $node->nid));
}
foreach ($all_grants as $module => $module_grants) {
$sql = "{$base_sql} OR ({$module_grants})) AND grant_{$op} >=1";
// Effectively AND module_grants together by breaking loop as soon as one fails
// A single SQL statement may be slightly quicker but won't tells us
// which of the modules denied access. This is useful debug feedback.
$result = db_result(db_query($sql, $node->nid));
//drupal_set_message("'$op' access=$result by $module-grants: '$node->title'", 'warning');
if ($result == 0) {
break;
}
}
return $result;
}
/**
* Return a map keyed by module name of SQL clauses representing the
* grants associated with the module, as returned by that module's
* hook_node_grants().
*
* @param $op
* The operation, i.e 'view', 'update' or 'delete'
* @param $account
* User account object
* @return
* Array of module grants SQL
*/
function grants_by_module($op, $account) {
$hook = 'node_grants';
$all_grants = array();
foreach (module_implements($hook) as $module) {
$module_grants = array();
foreach (module_invoke($module, $hook, $account, $op) as $realm => $gids) {
foreach ($gids as $gid) {
$module_grants[] = "(gid={$gid} AND realm='{$realm}')";
}
}
// Within a module OR the gid/realm combinations together
$module_sql = implode(' OR ', $module_grants);
$all_grants[$module] = $module_sql;
}
return $all_grants;
}
/**
*
* Menu callback to list all content viewable to the logged-in user.
*/
function module_grants_viewable_nodes() {
return theme('module_grants_viewable_nodes');
}
/**
* Menu callback to list all content editable to the logged-in user
*/
function module_grants_editable_nodes() {
return theme('module_grants_editable_nodes');
}
/**
* Implementation of hook_theme().
*/
function module_grants_theme() {
return array(
'module_grants_viewable_nodes' => array(
'arguments' => array(),
),
'module_grants_editable_nodes' => array(
'arguments' => array(),
),
);
}
/**
* Display in a table a summary of all content viewable to the logged-in user.
*
* @ingroup themeable
*/
function theme_module_grants_viewable_nodes() {
$nodes = get_nodes('view');
return _theme_nodes($nodes);
}
/**
* Display in a table a summary of all content editable to the logged-in user.
*
* @ingroup themeable
*/
function theme_module_grants_editable_nodes() {
$nodes = get_nodes('update');
return _theme_nodes($nodes);
}
/**
* Theme the passed-in nodes as a table.
*
* @param $nodes
* Array of nodes to display.
* @return
* Themed table HTML or a paragraph saying 'No content found'.
*/
function _theme_nodes($nodes) {
if (count($nodes) > 0) {
$header = array(
t('Title'),
t('Type'),
t('Last updated'),
t('By'),
t('Published?'),
);
$show_taxonomy_terms = module_exists('taxonomy');
$show_workflow_state = module_exists('workflow');
if ($show_taxonomy_terms) {
$header[] = t('Term');
}
if ($show_workflow_state) {
$header[] = t('Workflow state');
}
$rows = array();
$page_link = user_access('view revisions') ? 'revisions' : 'view';
foreach ($nodes as $node) {
$row = array(
l($node->title, "node/{$node->nid}/{$page_link}"),
check_plain(node_get_types('name', $node)),
format_date($node->timestamp),
theme('username', user_load(array(
'uid' => $node->uid,
))),
$node->status ? t('Yes') : t('No'),
);
if ($show_taxonomy_terms) {
$row[] = empty($node->term) ? '' : check_plain($node->term);
}
if ($show_workflow_state) {
$row[] = empty($node->state) ? t('No state') : check_plain($node->state);
}
$rows[] = $row;
}
return theme('table', $header, $rows);
}
return '<p>' . t('No content found.') . '</p>';
}
/**
* Retrieve a list of nodes or revisions accessible to the logged-in user via
* the supplied operation.
*
* @param $op
* Operation, one of 'view', 'edit' or 'delete'
* @param $pending
* Boolean indicating whether only pending or all nodes should be
* returned; a pending node is defined as a node that has a revision newer
* than the current.
* @return
* An array of node objects each containing nid, content type, published flag,
* user id title+vid+user_id+timestamp of the current revision, plus taxonomy
* term(s) and workflow state, if these modules are installed and enabled.
*
* @todo
* Allow paging, improve performance
*/
function get_nodes($op, $pending = FALSE) {
$sql_select = 'SELECT n.nid, n.type, n.status, r.title, r.uid, r.timestamp';
$sql_from = ' FROM {node} n INNER JOIN {node_revisions} r ' . ($pending ? 'ON n.nid=r.nid' : 'ON n.vid=r.vid');
$sql_where = $pending ? ' WHERE r.vid>n.vid OR (r.vid=n.vid AND n.status=0)' : '';
$sql_order = ' ORDER BY r.timestamp DESC';
$include_taxonomy_terms = module_exists('taxonomy');
$include_workflow_state = module_exists('workflow');
if ($include_taxonomy_terms) {
$sql_select .= ', td.name AS term';
$sql_from .= ' LEFT JOIN {term_node} tn ON n.vid=tn.vid LEFT JOIN {term_data} td ON tn.tid=td.tid';
}
if ($include_workflow_state) {
$sql_select .= ', ws.state';
$sql_from .= ' LEFT JOIN {workflow_node} wn ON wn.nid=n.nid LEFT JOIN {workflow_states} ws ON wn.sid=ws.sid';
}
$sql = $sql_select . $sql_from . $sql_where . $sql_order;
$node_query_result = db_query_range($sql, 0, 1000);
$nodes = array();
while ($node = db_fetch_object($node_query_result)) {
if (module_grants_node_access($op, $node)) {
// @todo rework into a single query from hell?
if (empty($nodes[$node->nid])) {
$nodes[$node->nid] = $node;
}
elseif ($include_taxonomy_terms && !empty($node->term)) {
// If a node has more than one taxonomy term, these will be returned by
// the query as seperate objects differing only in their term.
$existing_node = $nodes[$node->nid];
$existing_node->term .= '/' . $node->term;
}
}
}
return $nodes;
}
Functions
Name | Description |
---|---|
get_nodes | Retrieve a list of nodes or revisions accessible to the logged-in user via the supplied operation. |
grants_by_module | Return a map keyed by module name of SQL clauses representing the grants associated with the module, as returned by that module's hook_node_grants(). |
module_grants_editable_nodes | Menu callback to list all content editable to the logged-in user |
module_grants_menu | Implementation of hook_menu(). |
module_grants_menu_alter | Implementation of hook_menu_alter(). |
module_grants_node_access | Similar to node_access() in node.module but ANDs rather than ORs grants together on a per module base to create more natural behaviour. Also makes sure that published and unpublished content are treated in the same way, i.e. that grants are checked in… |
module_grants_node_revision_access | Menu options dealing with revisions have their revision-specific permission checked before being tested for the associated node-specific operation. Return a boolean indicating whether the current user has the requested permission AND access to the… |
module_grants_perm | Implementation of hook_perm(). |
module_grants_theme | Implementation of hook_theme(). |
module_grants_viewable_nodes | Menu callback to list all content viewable to the logged-in user. |
theme_module_grants_editable_nodes | Display in a table a summary of all content editable to the logged-in user. |
theme_module_grants_viewable_nodes | Display in a table a summary of all content viewable to the logged-in user. |
_theme_nodes | Theme the passed-in nodes as a table. |