Newer
Older
<?php
/**
* @file
*
* This module gives developers feedback as to what their
* node_access table contains, and which nodes are protected or
* visible to the public.
*/
define('DNA_ACCESS_VIEW', 'view devel_node_access information');
Hans Salvisberg
committed
function devel_node_access_permission() {
return array(
'view devel_node_access information' => array(
'description' => t('View the node access information blocks on node pages and the summary page.'),
'title' => t('Access DNA information'),
),
);
}
/**
* Implementation of hook_help().
*/
function devel_node_access_help($path, $arg) {
switch ($path) {
case 'admin/settings/modules#description':
return t('Development helper for node_access table');
break;
case 'admin/help#devel_node_access':
Hans Salvisberg
committed
$output = '<p>' . t('This module helps in site development. Specifically, when an access control module is used to limit access to some or all nodes, this module provides some feedback showing the node_access table in the database.') . "</p>\n";
$output .= '<p>' . t('The node_access table is one method Drupal provides to hide content from some users while displaying it to others. By default, Drupal shows all nodes to all users. There are a number of optional modules which may be installed to hide content from some users.') . "</p>\n";
$output .= '<p>' . t('If you have not installed any of these modules, you really have no need for the devel_node_access module. This module is intended for use during development, so that developers and admins can confirm that the node_access table is working as expected. You probably do not want this module enabled on a production site.') . "</p>\n";
$output .= '<p>' . t('This module provides two blocks. One called Devel Node Access by User is visible when a single node is shown on a page. This block shows which users can view, update or delete the node shown. Note that this block uses an inefficient algorithm to produce its output. You should only enable this block on sites with very few user accounts.') . "</p>\n";
$output .= '<p>' . t('The second block provided by this module shows the entries in the node_access table for any nodes shown on the current page. You can enable the debug mode on the <a href="@settings_page">settings page</a> to display much more information, but this can cause considerable overhead. Because the tables shown are wide, it is recommended to enable the blocks in the page footer rather than a sidebar.',
array('@settings_page' => url('admin/config/development/devel', array('fragment' => 'edit-devel-node-access-debug-mode')))
Hans Salvisberg
committed
) . "</p>\n";
$output .= '<p>' . t('This module also provides a <a href="@summary_page">summary page</a> which shows general information about your node_access table. If you have installed the Views module, you may browse node_access by realm.',
array('@summary_page' => url('devel/node_access/summary'))
Hans Salvisberg
committed
) . "</p>\n";
}
}
Moshe Weitzman
committed
function devel_node_access_menu() {
$items = array();
Moshe Weitzman
committed
Hans Salvisberg
committed
// Add this to the custom menu 'devel' created by devel module.
Moshe Weitzman
committed
$items['devel/node_access/summary'] = array(
Hans Salvisberg
committed
'title' => 'Node_access summary',
'page callback' => 'dna_summary',
'access arguments' => array(DNA_ACCESS_VIEW),
'menu_name' => 'devel',
);
Moshe Weitzman
committed
if (!module_exists('devel')) {
Hans Salvisberg
committed
// We have to create the 'devel' menu ourselves.
$menu = array(
'menu_name' => 'devel',
'title' => t('Development'),
'description' => t('Development link'),
);
menu_save($menu);
Hans Salvisberg
committed
$items['admin/config/development/devel'] = array(
'title' => 'Devel settings',
Hans Salvisberg
committed
'description' => 'Helper pages and blocks to assist Drupal developers and admins with node_access. The devel blocks can be managed via the <a href="' . url('admin/structure/block') . '">block administration</a> page.',
'page callback' => 'drupal_get_form',
'page arguments' => array('devel_node_access_admin_settings'),
'access arguments' => array('administer site configuration'),
Hans Salvisberg
committed
'menu_name' => 'devel',
);
}
return $items;
}
function devel_node_access_admin_settings() {
$form = array();
return system_settings_form($form);
}
function devel_node_access_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'devel_admin_settings' || $form_id == 'devel_node_access_admin_settings') {
$form['devel_node_access_debug_mode'] = array(
'#type' => 'checkbox',
'#title' => t('Devel Node Access debug mode'),
'#default_value' => variable_get('devel_node_access_debug_mode', FALSE),
Hans Salvisberg
committed
'#description' => t('Debug mode verifies the grant records in the node_access table against those that would be set by running !Rebuild_permissions, and displays them all; this can cause considerable overhead.', array('!Rebuild_permissions' => l('[' . $tr('Rebuild permissions') . ']', 'admin/reports/status/rebuild'))),
);
// push these down:
Hans Salvisberg
committed
$form['buttons']['#weight'] = 1;
}
}
function dna_summary() {
// Warn user if they have any entries that could grant access to all nodes
Hans Salvisberg
committed
$result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid = 0');
Moshe Weitzman
committed
$rows = array();
Hans Salvisberg
committed
foreach ($result as $row) {
Moshe Weitzman
committed
$rows[] = array($row->realm);
}
Hans Salvisberg
committed
$output .= '<h3>' . t('Access Granted to All Nodes (All Users)') . "</h3>\n";
$output .= '<p>' . t('Your node_access table contains entries that may be granting all users access to all nodes. Depending on which access control module(s) you use, you may want to delete these entries. If you are not using an access control module, you should probably leave these entries as is.') . "</p>\n";
$headers = array(t('realm'));
Hans Salvisberg
committed
$output .= theme('table', array('header' => $headers, 'rows' => $rows));
$access_granted_to_all_nodes = TRUE;
}
// how many nodes are not represented in the node_access table
Hans Salvisberg
committed
$num = db_query('SELECT COUNT(n.nid) AS num_nodes FROM {node} n LEFT JOIN {node_access} na ON n.nid = na.nid WHERE na.nid IS NULL')->fetchField();
if ($num) {
$output .= '<h3>' . t('Legacy Nodes') . "</h3>\n";
$output .= '<p>' .
Hans Salvisberg
committed
t('You have !num nodes in your node table which are not represented in your node_access table. If you have an access control module installed, these nodes may be hidden from all users. This could be caused by publishing nodes before enabling the access control module. If this is the case, manually updating each node should add it to the node_access table and fix the problem.', array('!num' => l($num, 'devel/node_access/view/NULL')))
Hans Salvisberg
committed
. "</p>\n";
if (!empty($access_granted_to_all_nodes)) {
Hans Salvisberg
committed
$output .= '<p>' .
t('This issue may be masked by the one above, so look into the former first.')
Hans Salvisberg
committed
. "</p>\n";
}
else {
Hans Salvisberg
committed
$output .= '<h3>' . t('All Nodes Represented') . "</h3>\n";
$output .= '<p>' . t('All nodes are represented in the node_access table.') . "</p>\n";
}
// a similar warning to the one above, but slightly more specific
Hans Salvisberg
committed
$result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid <> 0');
Moshe Weitzman
committed
$rows = array();
Hans Salvisberg
committed
foreach ($result as $row) {
Moshe Weitzman
committed
$rows[] = array($row->realm);
}
Hans Salvisberg
committed
$output .= '<h3>' . t('Access Granted to All Nodes (Some Users)') . "</h3>\n";
$output .= '<p>' . t('Your node_access table contains entries that may be granting some users access to all nodes. This may be perfectly normal, depending on which access control module(s) you use.') . "</p>\n";
$headers = array(t('realm'));
Hans Salvisberg
committed
$output .= theme('table', array('header' => $headers, 'rows' => $rows));
}
// find specific nodes which may be visible to all users
Hans Salvisberg
committed
$result = db_query('SELECT DISTINCT realm, COUNT(DISTINCT nid) as node_count FROM {node_access} WHERE gid = 0 AND nid > 0 GROUP BY realm');
Moshe Weitzman
committed
$rows = array();
Hans Salvisberg
committed
foreach ($result as $row) {
Moshe Weitzman
committed
$rows[] = array($row->realm,
Hans Salvisberg
committed
array('data' => $row->node_count,
'align' => 'center'));
Moshe Weitzman
committed
}
Hans Salvisberg
committed
$output .= '<h3>' . t('Access Granted to Some Nodes') . "</h3>\n";
$output .= '<p>' .
Hans Salvisberg
committed
t('The following realms appear to grant all users access to some specific nodes. This may be perfectly normal, if some of your content is available to the public.')
Hans Salvisberg
committed
. "</p>\n";
$headers = array(t('realm'), t('public nodes'));
Hans Salvisberg
committed
$output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Public Nodes')));
}
// find specific nodes protected by node_access table
Hans Salvisberg
committed
$result = db_query('SELECT DISTINCT realm, COUNT(DISTINCT nid) as node_count FROM {node_access} WHERE gid <> 0 AND nid > 0 GROUP BY realm');
Moshe Weitzman
committed
$rows = array();
Hans Salvisberg
committed
foreach ($result as $row) {
// no Views yet:
//$rows[] = array(l($row->realm, "devel/node_access/view/$row->realm"),
$rows[] = array($row->realm,
Hans Salvisberg
committed
array('data' => $row->node_count,
'align' => 'center'));
Moshe Weitzman
committed
}
Hans Salvisberg
committed
$output .= '<h3>' . t('Summary by Realm') . "</h3>\n";
$output .= '<p>' . t('The following realms grant limited access to some specific nodes.') . "</p>\n";
$headers = array(t('realm'), t('private nodes'));
Hans Salvisberg
committed
$output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Protected Nodes')));
}
return $output;
}
function dna_visible_nodes($nid = NULL) {
static $nids = array();
if ($nid) {
Hans Salvisberg
committed
$nids[$nid] = $nid;
}
return $nids;
}
function devel_node_access_node_view($node, $build_mode) {
// remember this node, for display in our block
dna_visible_nodes($node->nid);
}
Hans Salvisberg
committed
function _devel_node_access_module_invoke_all() { // array and scalar returns
Hans Salvisberg
committed
$args = func_get_args();
Hans Salvisberg
committed
$hook = $args[0];
unset($args[0]);
Hans Salvisberg
committed
$return = array();
foreach (module_implements($hook) as $module) {
Hans Salvisberg
committed
$function = $module . '_' . $hook;
if (function_exists($function)) {
$result = call_user_func_array($function, $args);
if (isset($result)) {
if (is_array($result)) {
foreach ($result as $key => $value) {
// add name of module that returned the value:
$result[$key]['#module'] = $module;
}
}
else {
// build array with result keyed by $module:
$result = array($module => $result);
}
$return = array_merge_recursive($return, $result);
Hans Salvisberg
committed
}
}
}
return $return;
}
function devel_node_access_block_info() {
$blocks['dna_node'] = array(
'info' => t('Devel Node Access'),
'region' => 'footer',
'status' => 1,
'cache' => DRUPAL_NO_CACHE,
);
$blocks['dna_user'] = array(
'info' => t('Devel Node Access by User'),
'region' => 'footer',
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
function devel_node_access_block_view($delta) {
Hans Salvisberg
committed
global $user;
Hans Salvisberg
committed
global $theme_key;
static $block1_visible, $hint = '';
if (!isset($block1_visible)) {
$block1_visible = db_query("SELECT status FROM {block} WHERE module = 'devel_node_access' AND delta = 'dna_user' AND theme = :theme", array(':theme' => $theme_key))->fetchField();
if (!$block1_visible) {
$hint = t('For per-user access permissions enable the second DNA <a href="@block">block</a>.', array('@block' => url('admin/structure/block')));
}
}
if (!user_access(DNA_ACCESS_VIEW)) {
return;
}
switch ($delta) {
case 'dna_node':
if (!count(dna_visible_nodes())) {
Hans Salvisberg
committed
return;
}
// include rows where nid == 0
$nids = array_merge(array(0 => 0), dna_visible_nodes());
Hans Salvisberg
committed
$query = db_select('node_access', 'na');
$query
->fields('na')
->condition('na.nid', $nids, 'IN')
->orderBy('na.nid')
->orderBy('na.realm')
->orderBy('na.gid');
$nodes = node_load_multiple($nids);
if (!variable_get('devel_node_access_debug_mode', FALSE)) {
$headers = array(t('node'), t('realm'), t('gid'), t('view'), t('update'), t('delete'), t('explained'));
$rows = array();
Hans Salvisberg
committed
foreach ($query->execute() as $row) {
$explained = module_invoke_all('node_access_explain', $row);
Hans Salvisberg
committed
$rows[] = array('<a href="#node-'. $row->nid .'">'. _devel_node_access_get_node_title($nodes[$row->nid]) .'</a>',
$row->realm,
$row->gid,
$row->grant_view,
$row->grant_update,
$row->grant_delete,
Hans Salvisberg
committed
implode('<br />', $explained));
Hans Salvisberg
committed
$output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('style' => 'text-align: left')));
$hint = t('To see more details enable <a href="@debug_mode">debug mode</a>.', array('@debug_mode' => url('admin/config/development/devel', array('fragment' => 'edit-devel-node-access-debug-mode')))) . ' ' . $hint;
}
else {
$tr = 't';
Hans Salvisberg
committed
$variables = array('!na' => '{node_access}');
$states = array(
Hans Salvisberg
committed
'default' => array(t('default'), 'ok', t('Default grant supplied by core in the absence of any other non-empty grants, in !na.', $variables)),
'ok' => array(t('ok'), 'ok', t('Highest priority grant, in !na.', $variables)),
'static' => array(t('static'), 'ok', t('Non-standard grant in !na.', $variables)),
'unexpected' => array(t('unexpected'), 'warning', t('The 0/0/all/... grant applies to all nodes and all users -- usually it should not be present if any node access module is active!')),
Hans Salvisberg
committed
'ignored' => array(t('ignored'), 'warning', t('Lower priority grant, not in !na and thus ignored.', $variables)),
'empty' => array(t('empty'), 'warning', t('Does not grant any access, but could block lower priority grants; not in !na.', $variables)),
'missing' => array(t('missing'), 'error', t("Should be in !na but isn't!", $variables)),
'illegitimate' => array(t('illegitimate'), 'error', t('Should NOT be in !na because of lower priority!', $variables)),
'alien' => array(t('alien'), 'error', t('Should NOT be in !na because of unknown origin!', $variables)),
);
$headers = array(
array('data' => t('node'), 'style' => array('text-align: right')),
array('data' => t('prio'), 'style' => array('text-align: right')),
t('status'), t('realm'),
array('data' => t('gid'), 'style' => array('text-align: right')),
t('view'), t('update'), t('delete'), t('explained')
);
$active_grants = array();
Hans Salvisberg
committed
foreach ($query->execute() as $active_grant) {
$active_grants[$active_grant->nid][$active_grant->realm][$active_grant->gid] = $active_grant;
}
$all_grants = $checked_grants = $checked_status = array();
foreach ($nids as $nid) {
$acquired_grants_nid = array();
Hans Salvisberg
committed
if ($node = node_load($nid)) {
// check node_access_acquire_grants()
$grants = _devel_node_access_module_invoke_all('node_access_records', $node);
drupal_alter('node_access_records', $grants, $node);
if (!empty($grants)) {
$top_priority = NULL;
foreach ($grants as $grant) {
$priority = intval($grant['priority']);
$top_priority = (isset($top_priority) ? max($top_priority, $priority) : $priority);
$grant['priority'] = (isset($grant['priority']) ? $priority : '– ');
$acquired_grants_nid[$priority][$grant['realm']][$grant['gid']] = $grant + array(
Hans Salvisberg
committed
'#title' => _devel_node_access_get_node_title($node),
'#module' => (isset($grant['#module']) ? $grant['#module'] : ''),
);
}
krsort($acquired_grants_nid);
Hans Salvisberg
committed
}
// check node_access_grants()
$checked_status[$nid] = $node->status;
if ($node->nid && $node->status) {
foreach (array('view', 'update', 'delete') as $op) {
$grants = _devel_node_access_module_invoke_all('node_grants', $user, $op);
drupal_alter('node_grants', $grants, $user, $op);
$checked_grants[$nid][$op] = array_merge(array('all' => array(0)), $grants);
Hans Salvisberg
committed
}
}
// check for grants in the node_access table that aren't returned by node_access_acquire_grants()
$found = FALSE;
if (isset($active_grants[$nid])) {
foreach ($active_grants[$nid] as $realm => $active_grants_realm) {
foreach ($active_grants_realm as $gid => $active_grant) {
$count_nonempty_grants = 0;
foreach ($acquired_grants_nid as $priority => $acquired_grants_nid_priority) {
if (isset($acquired_grants_nid_priority[$realm][$gid])) {
$found = TRUE;
Hans Salvisberg
committed
}
}
if ($acquired_grants_nid_priority = reset($acquired_grants_nid)) { // highest priority only
foreach ($acquired_grants_nid_priority as $acquired_grants_nid_priority_realm) {
foreach ($acquired_grants_nid_priority_realm as $acquired_grants_nid_priority_realm_gid) {
$count_nonempty_grants += (!empty($acquired_grants_nid_priority_realm_gid['grant_view']) || !empty($acquired_grants_nid_priority_realm_gid['grant_update']) || !empty($acquired_grants_nid_priority_realm_gid['grant_delete']));
Hans Salvisberg
committed
}
}
}
Hans Salvisberg
committed
$fixed_grant = (array) $active_grant;
if ($count_nonempty_grants == 0 && $realm == 'all' && $gid == 0 ) {
Hans Salvisberg
committed
$fixed_grant += array(
'priority' => '–',
'state' => 'default',
);
}
elseif (!$found) {
Hans Salvisberg
committed
$acknowledged = _devel_node_access_module_invoke_all('node_access_acknowledge', $fixed_grant);
if (empty($acknowledged)) {
// no one acknowledged this record, mark it as alien:
$fixed_grant += array(
'priority' => '?',
'state' => 'alien',
);
}
else {
// at least one module acknowledged the record, attribute it to the first one:
$fixed_grant += array(
'priority' => '–',
'state' => 'static',
'#module' => reset(array_keys($acknowledged)),
);
}
}
else {
continue;
}
$fixed_grant += array(
'nid' => $nid,
Hans Salvisberg
committed
'#title' => _devel_node_access_get_node_title($node, FALSE),
);
$all_grants[] = $fixed_grant;
Hans Salvisberg
committed
}
}
}
// order grants and evaluate their status
foreach ($acquired_grants_nid as $priority => $acquired_grants_priority) {
ksort($acquired_grants_priority);
foreach ($acquired_grants_priority as $realm => $acquired_grants_realm) {
ksort($acquired_grants_realm);
foreach ($acquired_grants_realm as $gid => $acquired_grant) {
if ($priority == $top_priority) {
if (empty($acquired_grant['grant_view']) && empty($acquired_grant['grant_update']) && empty($acquired_grant['grant_delete'])) {
$acquired_grant['state'] = 'empty';
}
else {
$acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'ok' : 'missing');
if ($acquired_grant['state'] == 'ok') {
foreach (array('view', 'update', 'delete') as $op) {
$active_grant = (array) $active_grants[$nid][$realm][$gid];
if (empty($acquired_grant["grant_$op"]) != empty($active_grant["grant_$op"]) ) {
$acquired_grant["grant_$op!"] = $active_grant["grant_$op"];
Hans Salvisberg
committed
}
}
}
}
}
else {
$acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'illegitimate' : 'ignored');
Hans Salvisberg
committed
}
$all_grants[] = $acquired_grant + array('nid' => $nid);
Hans Salvisberg
committed
}
}
}
}
// fill in the table rows
$rows = array();
$error_count = 0;
foreach ($all_grants as $grant) {
$row = new stdClass();
$row->nid = $grant['nid'];
$row->title = $grant['#title'];
$row->priority = $grant['priority'];
$row->state = array('data' => $states[$grant['state']][0], 'title' => $states[$grant['state']][2]);
$row->realm = $grant['realm'];
$row->gid = $grant['gid'];
$row->grant_view = $grant['grant_view'];
$row->grant_update = $grant['grant_update'];
$row->grant_delete = $grant['grant_delete'];
Hans Salvisberg
committed
$row->explained = implode('<br />', module_invoke_all('node_access_explain', $row));
Hans Salvisberg
committed
unset($row->title); // possibly needed above
if ($row->nid == 0 && $row->gid == 0 && $row->realm == 'all' && count($all_grants) > 1) {
$row->state = array('data' => $states['unexpected'][0], 'title' => $states['unexpected'][2]);
$class = $states['unexpected'][1];
}
else {
$class = $states[$grant['state']][1];
}
$error_count += ($class == 'error');
$row = (array) $row;
foreach (array('view', 'update', 'delete') as $op) {
$row["grant_$op"] = array('data' => $row["grant_$op"]);
if ((isset($checked_grants[$grant['nid']][$op][$grant['realm']]) && in_array($grant['gid'], $checked_grants[$grant['nid']][$op][$grant['realm']]) || ($row['nid'] == 0 && $row['gid'] == 0 && $row['realm'] == 'all')) && !empty($row["grant_$op"]['data'])) {
$row["grant_$op"]['data'] .= '′';
$row["grant_$op"]['title'] = t('This entry grants access to this node to this user.');
Hans Salvisberg
committed
}
if (isset($grant["grant_$op!"])) {
Hans Salvisberg
committed
$row["grant_$op"]['data'] = $grant["grant_$op!"] . '>' . $row["grant_$op"]['data'];
$row["grant_$op"]['class'][] = 'error';
Hans Salvisberg
committed
}
Hans Salvisberg
committed
$row['nid'] = '<a href="#node-'. $grant['nid'] .'">'. $row['nid'] .'</a>';
foreach (array('nid', 'priority', 'gid') as $key) {
$row[$key] = array('data' => $row[$key], 'style' => 'text-align: right');
}
$row['nid']['title'] = $grant['#title'];
Hans Salvisberg
committed
$row['realm'] = (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0 ? '' : $grant['#module'] . ':<br />') . $grant['realm'];
$rows[] = array('data' => array_values($row), 'class' => array('even', $class));
Hans Salvisberg
committed
$output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('class' => array('system-status-report'), 'style' => 'text-align: left;')));
Hans Salvisberg
committed
$output .= theme_form_element(array('element' => array('#value' => '', '#description' => t('(Some of the table elements provide additional information if you hover your mouse over them.)'), '#children' => NULL)));
if ($error_count > 0) {
Hans Salvisberg
committed
$variables['!Rebuild_permissions'] = '<a href="' . url('admin/reports/status/rebuild') . '">' . $tr('Rebuild permissions') . '</a>';
$output .= theme_form_element(array('element' => array('#value' => '<div class="error">' . t("You have errors in your !na table! You may be able to fix these for now by running !Rebuild_permissions, but this is likely to destroy the evidence and make it impossible to identify the underlying issues. If you don't fix those, the errors will probably come back again. <br /> DON'T do this just yet if you intend to ask for help with this situation.", $variables) . '</div>', '#children' => NULL)));
}
Hans Salvisberg
committed
// Explain whether access is granted or denied, and why (using code from node_access()).
Hans Salvisberg
committed
array_shift($nids); // remove the 0
$accounts = array();
$variables += array(
'!username' => theme('username', array('account' => $user)),
'%uid' => $user->uid,
);
if (user_access('bypass node access')) {
$variables['%bypass_node_access'] = $tr('bypass node access');
$output .= t('!username has the %bypass_node_access permission and thus full access to all nodes.', $variables) . '<br /> ';
}
else {
Hans Salvisberg
committed
$variables['!list'] = '<div style="margin-left: 2em">' . _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) . '</div>';
Hans Salvisberg
committed
$variables['%access'] = 'view';
$output .= "\n<div style='text-align: left' title='" . t('These are the grants returned by hook_node_grants() for this user.') . "'>" . t('!username (user %uid) can use these grants for %access access (if they are present above): !list', $variables) . "</div>\n";
Hans Salvisberg
committed
$accounts[] = $user;
}
if (arg(0) == 'node' && is_numeric(arg(1)) && !$block1_visible) { // only for single nodes
if (user_is_logged_in()) {
$accounts[] = user_load(0); // Anonymous, too
Hans Salvisberg
committed
foreach ($accounts as $account) {
$variables['!username'] = theme('username', array('account' => $account));
$output .= "\n<div style='text-align: left'>" . t("!username has the following access", $variables) . ' ';
$nid_items = array();
foreach ($nids as $nid) {
$op_items = array();
foreach (array('create', 'view', 'update', 'delete') as $op) {
$explain = _devel_node_access_explain_access($op, $nid, $account);
$op_items[] = "<div style='width: 5em; display: inline-block'>" . t('%op:', array('%op' => $op)) . ' </div>' . $explain[2];
Hans Salvisberg
committed
}
$nid_items[] = t('to node !nid:', array('!nid' => l($nid, 'node/' . $nid)))
Hans Salvisberg
committed
. "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $op_items, 'type' => 'ul')) . '</div>';
Hans Salvisberg
committed
}
Hans Salvisberg
committed
if (count($nid_items) == 1) {
$output .= $nid_items[0];
}
else {
$output .= "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $nid_items, 'type' => 'ul')) . '</div>';
}
$output .= "\n</div>\n";
Hans Salvisberg
committed
}
}
}
Hans Salvisberg
committed
Hans Salvisberg
committed
if (!empty($hint)) {
$output .= theme_form_element(array('element' => array('#value' => '', '#description' => '(' . $hint . ')', '#children' => NULL)));
}
$subject = t('node_access entries for nodes shown on this page');
Hans Salvisberg
committed
return array('subject' => $subject, 'content' => $output . '<br /><br />');
case 'dna_user':
// show which users can access this node
Hans Salvisberg
committed
if (arg(0) == 'node' && is_numeric($nid = arg(1)) && arg(2) == NULL && $node = node_load($nid)) {
Hans Salvisberg
committed
$node_type = node_type_get_type($node);
$headers = array(t('username'), '<span title="' . t("Create nodes of the '@Node_type' type.", array('@Node_type' => $node_type->name)) . '">' . t('create') . '</span>', t('view'), t('update'), t('delete'));
$rows = array();
// Find all users. The following operations are very inefficient, so we
// limit the number of users returned. It would be better to make a
// pager query, or at least make the number of users configurable. If
// anyone is up for that please submit a patch.
Hans Salvisberg
committed
$query = db_select('users', 'u');
$query
->distinct()
->fields('u')
->orderBy('access', 'DESC')
->range(0, 10);
foreach ($query->execute() as $data) {
$account = user_load($data->uid);
Hans Salvisberg
committed
$rows[] = array(theme('username', array('account' => $data)),
theme('dna_permission', _devel_node_access_explain_access('create', $nid, $account)),
theme('dna_permission', _devel_node_access_explain_access('view', $nid, $account)),
theme('dna_permission', _devel_node_access_explain_access('update', $nid, $account)),
theme('dna_permission', _devel_node_access_explain_access('delete', $nid, $account)),
);
}
if (count($rows)) {
Hans Salvisberg
committed
$output = theme('table', array('headers' => $headers, 'rows' => $rows, 'attributes' => array('style' => 'text-align: left')));
$output .= theme_form_element(array('element' => array('#value' => '', '#description' => t('(This table lists the most-recently active users. Hover your mouse over each result for more details.)'), '#children' => NULL)));
return array('subject' => t('Access permissions by user'),
'content' => $output);
}
Hans Salvisberg
committed
}
}
}
Hans Salvisberg
committed
/**
* Helper function that mimicks node.module's node_access() function.
Hans Salvisberg
committed
* Unfortunately, this needs to be updated manually whenever node.module changes!
Hans Salvisberg
committed
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
* @return
* An array suitable for theming with theme_dna_permission().
*/
function _devel_node_access_explain_access($op, $node, $account = NULL) {
global $user;
if (is_numeric($node) && !($node = node_load($node))) {
return array( FALSE, '???',
t('Unable to load the node – this should never happen!'),
);
}
if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
return array( FALSE, t('!NO: invalid $op', array('!NO' => t('NO'))),
t("'@op' is an invalid operation!", array('@op' => $op)),
);
}
if ($op == 'create' && is_object($node)) {
$node = $node->type;
}
if (!empty($account)) {
// To try to get the most authentic result we impersonate the given user!
// This may reveal bugs in other modules, leading to contradictory results.
$saved_user = $user;
drupal_save_session(FALSE);
$user = $account;
$result = _devel_node_access_explain_access($op, $node, NULL);
$user = $saved_user;
drupal_save_session(TRUE);
$second_opinion = node_access($op, $node, $account);
if ($second_opinion != $result[0]) {
$result[1] .= '<span class="' . ($second_opinion ? 'ok' : 'error') . '" title="DNA and Core seem to disagree on this item. This is a bug in either one of them and should be fixed! Try to look at this node as this user and check whether there is still disagreement.">*</span>';
}
return $result;
}
$variables = array(
'!NO' => t('NO'),
'!YES' => t('YES'),
);
if (user_access('bypass node access')) {
return array( TRUE,
t('!YES: bypass node access', $variables),
t("!YES: This user has the '!bypass_node_access' permission and may do everything with nodes.", $variables += array(
'!bypass_node_access' => t('bypass node access'),
)),
);
}
if (!user_access('access content')) {
return array( FALSE,
t('!NO: access content', $variables),
t("!NO: This user does not have the '!access_content' permission and is denied doing anything with content.", $variables += array(
'!access_content' => t('access content'),
)),
);
}
foreach (module_implements('node_access') as $module) {
$function = $module . '_node_access';
if (function_exists($function)) {
$result = $function($node, $op, $user);
if ($module == 'node') {
$module = 'node (permissions)';
}
if (isset($result)) {
if ($result === NODE_ACCESS_DENY) {
$denied_by[] = $module;
}
elseif ($result === NODE_ACCESS_ALLOW) {
$allowed_by[] = $module;
}
$access[] = $result;
}
}
}
$variables += array(
'@deniers' => (empty($denied_by) ? NULL : implode(', ', $denied_by)),
'@allowers' => (empty($allowed_by) ? NULL : implode(', ', $allowed_by)),
);
if (!empty($denied_by)) {
$variables += array(
'%module' => $denied_by[0],
);
return array( FALSE,
t('!NO: by %module', $variables),
(empty($allowed_by)
? t("!NO: hook_node_access() of the following module(s) denies this: @deniers.", $variables)
: t("!NO: hook_node_access() of the following module(s) denies this: @deniers – even though the following module(s) would allow it: @allowers.", $variables)),
);
}
if (!empty($allowed_by)) {
$variables += array(
'%module' => $allowed_by[0],
);
return array( TRUE,
t('!YES: by %module', $variables),
t("!YES: hook_node_access() of the following module(s) allows this: @allowers.", $variables),
);
}
if ($op == 'view' && !$node->status && user_access('view own unpublished content') && $user->uid == $node->uid && $user->uid != 0) {
return array( TRUE,
t('!YES: view own unpublished content', $variables),
t("!YES: The node is unpublished, but the user has the '!view_own_unpublished_content' permission.", $variables += array(
'!view_own_unpublished_content' => t('view own unpublished content'),
)),
);
}
if ($op != 'create' && $node->nid) {
if (node_access($op, $node)) { // delegate this part
return array( TRUE,
t('!YES: node access', $variables),
t('!YES: Node access allows this.', $variables),
);
}
else {
return array( FALSE,
t('!NO: node access', $variables),
t('!NO: Node access denies this.', $variables),
);
}
}
return array( FALSE,
t('!NO: no reason', $variables),
t("!NO: None of the checks resulted in allowing this, so it's denied.", $variables) .
($op == 'create' ? ' ' . t('This is most likely due to a withheld permission.') : ''),
);
}
/**
* Helper function to create a list of the grants returned by hook_node_grants().
*/
function _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) {
if (!empty($checked_status[$nid])) {
$cgs_by_realm = array();
foreach ($checked_grants[$nid]['view'] as $realm => $cg) {
if (isset($cg['#module'])) {
$module = $cg['#module'];
unset($cg['#module']);
if (!empty($module) && (strpos($realm, $module) !== 0)) {
$realm = $module . ':' . $realm;
}
}
$cgs_by_realm[$realm] = $realm . ': ' . implode(', ', $cg);
}
if (!empty($cgs_by_realm)) {
return theme('item_list', array('items' => array_values($cgs_by_realm), 'type' => 'ul'));
}
}
}
Moshe Weitzman
committed
/**
* Implementation of hook_node_access_explain().
Hans Salvisberg
committed
*/
Moshe Weitzman
committed
function devel_node_access_node_access_explain($row) {
if ($row->gid == 0 && $row->realm == 'all') {
foreach (array('view', 'update', 'delete') as $op) {
Hans Salvisberg
committed
$gop = 'grant_' . $op;
if (!empty($row->$gop)) {
$ops[] = $op;
}
}
if (empty($ops)) {
return '(No access granted to ' . ($row->nid == 0 ? 'any nodes.)' : 'this node.)');
Hans Salvisberg
committed
else {
Hans Salvisberg
committed
return 'All users may ' . implode('/', $ops) . ($row->nid == 0 ? ' all nodes.' : ' this node.');
Moshe Weitzman
committed
}
}
Hans Salvisberg
committed
/**
* Helper function to return a sanitized node title.
*/
function _devel_node_access_get_node_title($node, $clip_and_decorate = TRUE) {
if (isset($node)) {
if (isset($node->title[LANGUAGE_NONE][0]['value'])) {
$node_title = check_plain($node->title[LANGUAGE_NONE][0]['value']);
Hans Salvisberg
committed
if ($clip_and_decorate) {
if (drupal_strlen($node_title) > 20) {
$node_title = "<span title='node/$node->nid: $node_title'>" . drupal_substr($node_title, 0, 15) . '...</span>';
}
$node_title = '<span title="node/' . $node->nid . '">' . $node_title . '</span>';
}
return $node_title;
}
elseif (isset($node->nid)) {
return $node->nid;
}
}
return '—';
}
Hans Salvisberg
committed
/**
Hans Salvisberg
committed
*/
function devel_node_access_theme() {
return array(
'dna_permission' => array(
'arguments' => array('permission' => NULL),
Hans Salvisberg
committed
),
);
}
/**
* Indicate whether user has a permission or not.
*/
function theme_dna_permission($permission) {
Hans Salvisberg
committed
return '<span class="' . ($permission[0] ? 'ok' : 'error') . '" title="' . $permission[2] . '">' . $permission[1] . '</span>';