Tune alignment of table headers.
[project/devel.git] / devel_node_access.module
CommitLineData
361dbb76 1<?php
2120c1bd 2// $Id$
361dbb76 3/**
3c1264d3
JC
4 * @file
5 *
b5d6be8c 6 * This module gives developers feedback as to what their
361dbb76
DC
7 * node_access table contains, and which nodes are protected or
8 * visible to the public.
3c1264d3 9 *
361dbb76
DC
10 */
11
12define('DNA_ACCESS_VIEW', 'view devel_node_access information');
13
1094cb03 14function devel_node_access_permission() {
15 return array(
16 'view devel_node_access information' => array(
17 'description' => t('View the node access information blocks on node pages and the summary page.'),
18 'title' => t('Access DNA information'),
19 ),
20 );
361dbb76
DC
21}
22
23/**
24 * Implementation of hook_help().
25 */
91d809c6 26function devel_node_access_help($path, $arg) {
27 switch ($path) {
28 case 'admin/settings/modules#description':
29 return t('Development helper for node_access table');
30 break;
31 case 'admin/help#devel_node_access':
1094cb03 32 $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";
33 $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";
34 $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";
35 $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";
36 $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.',
17507063 37 array('@settings_page' => url('admin/config/development/devel', array('fragment' => 'edit-devel-node-access-debug-mode')))
1094cb03 38 ) . "</p>\n";
39 $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.',
17507063 40 array('@summary_page' => url('devel/node_access/summary'))
1094cb03 41 ) . "</p>\n";
91d809c6 42 return $output;
361dbb76
DC
43 }
44}
45
aa3e9ef2 46function devel_node_access_menu() {
361dbb76 47 $items = array();
aa3e9ef2 48
1094cb03 49 // Add this to the custom menu 'devel' created by devel module.
aa3e9ef2 50 $items['devel/node_access/summary'] = array(
edaf11c1 51 'title' => 'Node_access summary',
52 'page callback' => 'dna_summary',
53 'access arguments' => array(DNA_ACCESS_VIEW),
54 'menu_name' => 'devel',
55 );
aa3e9ef2 56
f72fc647 57 if (!module_exists('devel')) {
1094cb03 58 // We have to create the 'devel' menu ourselves.
59 $menu = array(
60 'menu_name' => 'devel',
61 'title' => t('Development'),
62 'description' => t('Development link'),
63 );
64 menu_save($menu);
65
90bc9415 66 $items['admin/config/development/devel'] = array(
67 'title' => 'Devel settings',
1094cb03 68 '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.',
f72fc647 69 'page callback' => 'drupal_get_form',
70 'page arguments' => array('devel_node_access_admin_settings'),
71 'access arguments' => array('administer site configuration'),
1094cb03 72 'menu_name' => 'devel',
f72fc647 73 );
74 }
75
361dbb76
DC
76 return $items;
77}
78
f72fc647 79function devel_node_access_admin_settings() {
80 $form = array();
81 return system_settings_form($form);
82}
83
84function devel_node_access_form_alter(&$form, $form_state, $form_id) {
17507063 85 $tr = 't';
f72fc647 86 if ($form_id == 'devel_admin_settings' || $form_id == 'devel_node_access_admin_settings') {
87 $form['devel_node_access_debug_mode'] = array(
88 '#type' => 'checkbox',
89 '#title' => t('Devel Node Access debug mode'),
90 '#default_value' => variable_get('devel_node_access_debug_mode', FALSE),
1094cb03 91 '#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'))),
f72fc647 92 );
93 // push these down:
90bc9415 94 $form['buttons']['#weight'] = 1;
f72fc647 95 }
96}
97
361dbb76
DC
98function dna_summary() {
99 // Warn user if they have any entries that could grant access to all nodes
91d809c6 100 $output = '';
1094cb03 101 $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid = 0');
aa3e9ef2 102 $rows = array();
1094cb03 103 foreach ($result as $row) {
aa3e9ef2 104 $rows[] = array($row->realm);
105 }
91d809c6 106 if (!empty($rows)) {
1094cb03 107 $output .= '<h3>' . t('Access Granted to All Nodes (All Users)') . "</h3>\n";
108 $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";
91d809c6 109 $headers = array(t('realm'));
1094cb03 110 $output .= theme('table', array('header' => $headers, 'rows' => $rows));
a2d2ff9f 111 $access_granted_to_all_nodes = TRUE;
361dbb76
DC
112 }
113
114 // how many nodes are not represented in the node_access table
1094cb03 115 $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();
116 if ($num) {
117 $output .= '<h3>' . t('Legacy Nodes') . "</h3>\n";
118 $output .= '<p>' .
edaf11c1 119 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')))
1094cb03 120 . "</p>\n";
a2d2ff9f 121 if (!empty($access_granted_to_all_nodes)) {
1094cb03 122 $output .= '<p>' .
a2d2ff9f 123 t('This issue may be masked by the one above, so look into the former first.')
1094cb03 124 . "</p>\n";
a2d2ff9f 125 }
361dbb76
DC
126 }
127 else {
1094cb03 128 $output .= '<h3>' . t('All Nodes Represented') . "</h3>\n";
129 $output .= '<p>' . t('All nodes are represented in the node_access table.') . "</p>\n";
361dbb76
DC
130 }
131
132
133 // a similar warning to the one above, but slightly more specific
edaf11c1 134 $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid <> 0');
aa3e9ef2 135 $rows = array();
1094cb03 136 foreach ($result as $row) {
aa3e9ef2 137 $rows[] = array($row->realm);
138 }
91d809c6 139 if (!empty($rows)) {
1094cb03 140 $output .= '<h3>' . t('Access Granted to All Nodes (Some Users)') . "</h3>\n";
141 $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";
91d809c6 142 $headers = array(t('realm'));
1094cb03 143 $output .= theme('table', array('header' => $headers, 'rows' => $rows));
361dbb76
DC
144 }
145
146
147 // find specific nodes which may be visible to all users
edaf11c1 148 $result = db_query('SELECT DISTINCT realm, COUNT(DISTINCT nid) as node_count FROM {node_access} WHERE gid = 0 AND nid > 0 GROUP BY realm');
aa3e9ef2 149 $rows = array();
1094cb03 150 foreach ($result as $row) {
aa3e9ef2 151 $rows[] = array($row->realm,
edaf11c1 152 array('data' => $row->node_count,
153 'align' => 'center'));
aa3e9ef2 154 }
91d809c6 155 if (!empty($rows)) {
1094cb03 156 $output .= '<h3>' . t('Access Granted to Some Nodes') . "</h3>\n";
157 $output .= '<p>' .
edaf11c1 158 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.')
1094cb03 159 . "</p>\n";
91d809c6 160 $headers = array(t('realm'), t('public nodes'));
1094cb03 161 $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Public Nodes')));
361dbb76 162 }
3c1264d3 163
361dbb76
DC
164
165 // find specific nodes protected by node_access table
edaf11c1 166 $result = db_query('SELECT DISTINCT realm, COUNT(DISTINCT nid) as node_count FROM {node_access} WHERE gid <> 0 AND nid > 0 GROUP BY realm');
aa3e9ef2 167 $rows = array();
1094cb03 168 foreach ($result as $row) {
f72fc647 169 // no Views yet:
170 //$rows[] = array(l($row->realm, "devel/node_access/view/$row->realm"),
171 $rows[] = array($row->realm,
edaf11c1 172 array('data' => $row->node_count,
173 'align' => 'center'));
aa3e9ef2 174 }
91d809c6 175 if (!empty($rows)) {
1094cb03 176 $output .= '<h3>' . t('Summary by Realm') . "</h3>\n";
177 $output .= '<p>' . t('The following realms grant limited access to some specific nodes.') . "</p>\n";
91d809c6 178 $headers = array(t('realm'), t('private nodes'));
1094cb03 179 $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Protected Nodes')));
361dbb76 180 }
3c1264d3 181
361dbb76
DC
182 return $output;
183}
184
09344f2f 185function dna_visible_nodes($nid = NULL) {
361dbb76
DC
186 static $nids = array();
187 if ($nid) {
edaf11c1 188 $nids[$nid] = $nid;
361dbb76
DC
189 }
190 return $nids;
191}
192
4749300a 193function devel_node_access_node_view($node, $build_mode) {
194 // remember this node, for display in our block
195 dna_visible_nodes($node->nid);
361dbb76
DC
196}
197
1094cb03 198function _devel_node_access_module_invoke_all() { // array and scalar returns
a6028226 199 $args = func_get_args();
1094cb03 200 $hook = $args[0];
201 unset($args[0]);
a6028226 202 $return = array();
203 foreach (module_implements($hook) as $module) {
1094cb03 204 $function = $module . '_' . $hook;
205 if (function_exists($function)) {
206 $result = call_user_func_array($function, $args);
207 if (isset($result)) {
208 if (is_array($result)) {
209 foreach ($result as $key => $value) {
210 // add name of module that returned the value:
211 $result[$key]['#module'] = $module;
212 }
213 }
214 else {
215 // build array with result keyed by $module:
216 $result = array($module => $result);
217 }
218 $return = array_merge_recursive($return, $result);
a6028226 219 }
a6028226 220 }
221 }
222 return $return;
223}
224
616d7cb9 225function devel_node_access_block_info() {
226 $blocks['dna_node'] = array(
227 'info' => t('Devel Node Access'),
228 'region' => 'footer',
229 'status' => 1,
230 'cache' => DRUPAL_NO_CACHE,
231 );
232 $blocks['dna_user'] = array(
233 'info' => t('Devel Node Access by User'),
234 'region' => 'footer',
235 'cache' => DRUPAL_NO_CACHE,
236 );
4749300a 237 return $blocks;
238}
239
616d7cb9 240function devel_node_access_block_view($delta) {
a6028226 241 global $user;
1094cb03 242 global $theme_key;
243 static $block1_visible, $hint = '';
244 if (!isset($block1_visible)) {
245 $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();
246 if (!$block1_visible) {
247 $hint = t('For per-user access permissions enable the second DNA <a href="@block">block</a>.', array('@block' => url('admin/structure/block')));
248 }
249 }
250
4749300a 251 if (!user_access(DNA_ACCESS_VIEW)) {
252 return;
253 }
254 switch ($delta) {
616d7cb9 255 case 'dna_node':
4749300a 256 if (!count(dna_visible_nodes())) {
edaf11c1 257 return;
258 }
b5d6be8c 259
4749300a 260 // include rows where nid == 0
261 $nids = array_merge(array(0 => 0), dna_visible_nodes());
1094cb03 262 $query = db_select('node_access', 'na');
1094cb03 263 $query
264 ->fields('na')
1094cb03 265 ->condition('na.nid', $nids, 'IN')
266 ->orderBy('na.nid')
267 ->orderBy('na.realm')
268 ->orderBy('na.gid');
2f19ebc0 269 $nodes = node_load_multiple($nids);
4749300a 270
271 if (!variable_get('devel_node_access_debug_mode', FALSE)) {
272 $headers = array(t('node'), t('realm'), t('gid'), t('view'), t('update'), t('delete'), t('explained'));
273 $rows = array();
1094cb03 274 foreach ($query->execute() as $row) {
4749300a 275 $explained = module_invoke_all('node_access_explain', $row);
2f19ebc0 276 $rows[] = array(_devel_node_access_get_node_title($nodes[$row->nid]),
4749300a 277 $row->realm,
278 $row->gid,
279 $row->grant_view,
280 $row->grant_update,
281 $row->grant_delete,
1094cb03 282 implode('<br />', $explained));
4749300a 283 }
1094cb03 284 $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('style' => 'text-align: left')));
285 $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;
4749300a 286 }
287 else {
288 $tr = 't';
1094cb03 289 $variables = array('!na' => '{node_access}');
4749300a 290 $states = array(
1094cb03 291 'default' => array(t('default'), 'ok', t('Default grant supplied by core in the absence of any other non-empty grants, in !na.', $variables)),
292 'ok' => array(t('ok'), 'ok', t('Highest priority grant, in !na.', $variables)),
293 'static' => array(t('static'), 'ok', t('Non-standard grant in !na.', $variables)),
4749300a 294 '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!')),
1094cb03 295 'ignored' => array(t('ignored'), 'warning', t('Lower priority grant, not in !na and thus ignored.', $variables)),
296 'empty' => array(t('empty'), 'warning', t('Does not grant any access, but could block lower priority grants; not in !na.', $variables)),
297 'missing' => array(t('missing'), 'error', t("Should be in !na but isn't!", $variables)),
298 'illegitimate' => array(t('illegitimate'), 'error', t('Should NOT be in !na because of lower priority!', $variables)),
299 'alien' => array(t('alien'), 'error', t('Should NOT be in !na because of unknown origin!', $variables)),
4749300a 300 );
de2ee8bd 301 $headers = array(
302 array('data' => t('node'), 'style' => array('text-align: right')),
303 array('data' => t('prio'), 'style' => array('text-align: right')),
304 t('status'), t('realm'),
305 array('data' => t('gid'), 'style' => array('text-align: right')),
306 t('view'), t('update'), t('delete'), t('explained')
307 );
4749300a 308 $active_grants = array();
1094cb03 309 foreach ($query->execute() as $active_grant) {
4749300a 310 $active_grants[$active_grant->nid][$active_grant->realm][$active_grant->gid] = $active_grant;
311 }
312 $all_grants = $checked_grants = $checked_status = array();
313 foreach ($nids as $nid) {
314 $acquired_grants_nid = array();
90bc9415 315 if ($node = node_load($nid)) {
4749300a 316 // check node_access_acquire_grants()
317 $grants = _devel_node_access_module_invoke_all('node_access_records', $node);
318 if (!empty($grants)) {
319 $top_priority = NULL;
320 foreach ($grants as $grant) {
321 $priority = intval($grant['priority']);
322 $top_priority = (isset($top_priority) ? max($top_priority, $priority) : $priority);
323 $grant['priority'] = (isset($grant['priority']) ? $priority : '&ndash;&nbsp;');
324 $acquired_grants_nid[$priority][$grant['realm']][$grant['gid']] = $grant + array(
1094cb03 325 '#title' => _devel_node_access_get_node_title($node),
4749300a 326 '#module' => (isset($grant['#module']) ? $grant['#module'] : ''),
327 );
328 }
329 krsort($acquired_grants_nid);
a6028226 330 }
4749300a 331 // check node_access_grants()
332 $checked_status[$nid] = $node->status;
333 if ($node->nid && $node->status) {
334 foreach (array('view', 'update', 'delete') as $op) {
335 $checked_grants[$nid][$op] = array_merge(
336 array('all' => array(0)),
337 _devel_node_access_module_invoke_all('node_grants', $user, $op)
338 );
339 }
a6028226 340 }
4749300a 341 }
342 // check for grants in the node_access table that aren't returned by node_access_acquire_grants()
343 $found = FALSE;
344 if (isset($active_grants[$nid])) {
345 foreach ($active_grants[$nid] as $realm => $active_grants_realm) {
346 foreach ($active_grants_realm as $gid => $active_grant) {
347 $count_nonempty_grants = 0;
348 foreach ($acquired_grants_nid as $priority => $acquired_grants_nid_priority) {
349 if (isset($acquired_grants_nid_priority[$realm][$gid])) {
350 $found = TRUE;
a6028226 351 }
352 }
4749300a 353 if ($acquired_grants_nid_priority = reset($acquired_grants_nid)) { // highest priority only
354 foreach ($acquired_grants_nid_priority as $acquired_grants_nid_priority_realm) {
355 foreach ($acquired_grants_nid_priority_realm as $acquired_grants_nid_priority_realm_gid) {
356 $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']));
a6028226 357 }
a6028226 358 }
359 }
1094cb03 360 $fixed_grant = (array) $active_grant;
4749300a 361 if ($count_nonempty_grants == 0 && $realm == 'all' && $gid == 0 ) {
1094cb03 362 $fixed_grant += array(
4749300a 363 'priority' => '&ndash;',
364 'state' => 'default',
365 );
366 }
367 elseif (!$found) {
1094cb03 368 $acknowledged = _devel_node_access_module_invoke_all('node_access_acknowledge', $fixed_grant);
369 if (empty($acknowledged)) {
370 // no one acknowledged this record, mark it as alien:
371 $fixed_grant += array(
372 'priority' => '?',
373 'state' => 'alien',
374 );
375 }
376 else {
377 // at least one module acknowledged the record, attribute it to the first one:
378 $fixed_grant += array(
379 'priority' => '&ndash;',
380 'state' => 'static',
381 '#module' => reset(array_keys($acknowledged)),
382 );
383 }
4749300a 384 }
385 else {
386 continue;
387 }
388 $fixed_grant += array(
389 'nid' => $nid,
1094cb03 390 '#title' => _devel_node_access_get_node_title($node, FALSE),
4749300a 391 );
392 $all_grants[] = $fixed_grant;
a6028226 393 }
4749300a 394 }
395 }
396 // order grants and evaluate their status
397 foreach ($acquired_grants_nid as $priority => $acquired_grants_priority) {
398 ksort($acquired_grants_priority);
399 foreach ($acquired_grants_priority as $realm => $acquired_grants_realm) {
400 ksort($acquired_grants_realm);
401 foreach ($acquired_grants_realm as $gid => $acquired_grant) {
402 if ($priority == $top_priority) {
403 if (empty($acquired_grant['grant_view']) && empty($acquired_grant['grant_update']) && empty($acquired_grant['grant_delete'])) {
404 $acquired_grant['state'] = 'empty';
405 }
406 else {
407 $acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'ok' : 'missing');
408 if ($acquired_grant['state'] == 'ok') {
409 foreach (array('view', 'update', 'delete') as $op) {
410 $active_grant = (array) $active_grants[$nid][$realm][$gid];
411 if (empty($acquired_grant["grant_$op"]) != empty($active_grant["grant_$op"]) ) {
412 $acquired_grant["grant_$op!"] = $active_grant["grant_$op"];
a6028226 413 }
414 }
415 }
a6028226 416 }
417 }
4749300a 418 else {
419 $acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'illegitimate' : 'ignored');
a6028226 420 }
4749300a 421 $all_grants[] = $acquired_grant + array('nid' => $nid);
a6028226 422 }
a6028226 423 }
4749300a 424 }
425 }
426 // fill in the table rows
427 $rows = array();
428 $error_count = 0;
429 foreach ($all_grants as $grant) {
430 $row = new stdClass();
431 $row->nid = $grant['nid'];
432 $row->title = $grant['#title'];
433 $row->priority = $grant['priority'];
434 $row->state = array('data' => $states[$grant['state']][0], 'title' => $states[$grant['state']][2]);
435 $row->realm = $grant['realm'];
436 $row->gid = $grant['gid'];
437 $row->grant_view = $grant['grant_view'];
438 $row->grant_update = $grant['grant_update'];
439 $row->grant_delete = $grant['grant_delete'];
1094cb03 440 $row->explained = implode('<br />', module_invoke_all('node_access_explain', $row));
4749300a 441 unset($row->title);
442 if ($row->nid == 0 && $row->gid == 0 && $row->realm == 'all' && count($all_grants) > 1) {
443 $row->state = array('data' => $states['unexpected'][0], 'title' => $states['unexpected'][2]);
444 $class = $states['unexpected'][1];
445 }
446 else {
447 $class = $states[$grant['state']][1];
448 }
449 $error_count += ($class == 'error');
450 $row = (array) $row;
451 foreach (array('view', 'update', 'delete') as $op) {
452 $row["grant_$op"] = array('data' => $row["grant_$op"]);
453 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'])) {
454 $row["grant_$op"]['data'] .= '&prime;';
455 $row["grant_$op"]['title'] = t('This entry grants access to this node to this user.');
deab5cc3 456 }
4749300a 457 if (isset($grant["grant_$op!"])) {
1094cb03 458 $row["grant_$op"]['data'] = $grant["grant_$op!"] . '&gt;' . $row["grant_$op"]['data'];
459 $row["grant_$op"]['class'][] = 'error';
a6028226 460 }
4749300a 461 }
462 foreach (array('nid', 'priority', 'gid') as $key) {
463 $row[$key] = array('data' => $row[$key], 'style' => 'text-align: right');
464 }
465 $row['nid']['title'] = $grant['#title'];
1094cb03 466 $row['realm'] = (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0 ? '' : $grant['#module'] . ':<br />') . $grant['realm'];
467 $rows[] = array('data' => array_values($row), 'class' => array('even', $class));
4749300a 468 }
1094cb03 469 $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('class' => array('system-status-report'), 'style' => 'text-align: left;')));
4749300a 470
1094cb03 471 $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)));
4749300a 472
473 if ($error_count > 0) {
1094cb03 474 $variables['!Rebuild_permissions'] = '<a href="' . url('admin/reports/status/rebuild') . '">' . $tr('Rebuild permissions') . '</a>';
475 $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)));
4749300a 476 }
477
1094cb03 478 // Explain whether access is granted or denied, and why (using code from node_access()).
17507063 479 $tr = 't';
1094cb03 480 array_shift($nids); // remove the 0
481 $accounts = array();
482 $variables += array(
483 '!username' => theme('username', array('account' => $user)),
484 '%uid' => $user->uid,
485 );
486
487 if (user_access('bypass node access')) {
488 $variables['%bypass_node_access'] = $tr('bypass node access');
489 $output .= t('!username has the %bypass_node_access permission and thus full access to all nodes.', $variables) . '<br />&nbsp;';
4749300a 490 }
491 else {
1094cb03 492 $variables['!list'] = '<div style="margin-left: 2em">' . _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) . '</div>';
493 $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 (if they are present above): !list', $variables) . "</div>\n";
494 $accounts[] = $user;
495 }
496 if (arg(0) == 'node' && is_numeric(arg(1)) && !$block1_visible) { // only for single nodes
497 if (user_is_logged_in()) {
498 $accounts[] = user_load(0); // Anonymous, too
4749300a 499 }
1094cb03 500 foreach ($accounts as $account) {
501 $variables['!username'] = theme('username', array('account' => $account));
502 $output .= "\n<div style='text-align: left'>" . t("!username has the following access", $variables) . ' ';
503 $nid_items = array();
504 foreach ($nids as $nid) {
505 $op_items = array();
506 foreach (array('create', 'view', 'update', 'delete') as $op) {
507 $explain = _devel_node_access_explain_access($op, $nid, $account);
508 $op_items[] = "<div style='width: 5em; display: inline-block'>" . t('%op:', array('%op' => $op)) . ' </div>' . $explain[2];
a6028226 509 }
1094cb03 510 $nid_items[] = t('to node !nid:', array('!nid' => l($nid, 'node/' . $nid)))
511 . "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $op_items, 'type' => 'ul')) . '</div>';
a6028226 512 }
1094cb03 513 if (count($nid_items) == 1) {
514 $output .= $nid_items[0];
515 }
516 else {
517 $output .= "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $nid_items, 'type' => 'ul')) . '</div>';
518 }
519 $output .= "\n</div>\n";
a6028226 520 }
4749300a 521 }
522 }
a6028226 523
1094cb03 524 if (!empty($hint)) {
525 $output .= theme_form_element(array('element' => array('#value' => '', '#description' => '(' . $hint . ')', '#children' => NULL)));
526 }
4749300a 527 $subject = t('node_access entries for nodes shown on this page');
1094cb03 528 return array('subject' => $subject, 'content' => $output . '<br /><br />');
4749300a 529
616d7cb9 530 case 'dna_user':
4749300a 531 // show which users can access this node
1094cb03 532 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == null) {
4749300a 533 $nid = arg(1);
534 $node = node_load($nid);
1094cb03 535 $node_type = node_type_get_type($node);
536 $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'));
4749300a 537 $rows = array();
538 // Find all users. The following operations are very inefficient, so we
539 // limit the number of users returned. It would be better to make a
540 // pager query, or at least make the number of users configurable. If
541 // anyone is up for that please submit a patch.
1094cb03 542 $query = db_select('users', 'u');
543 $query
544 ->distinct()
545 ->fields('u')
546 ->orderBy('access', 'DESC')
547 ->range(0, 10);
548 foreach ($query->execute() as $data) {
616d7cb9 549 $account = user_load($data->uid);
1094cb03 550 $rows[] = array(theme('username', array('account' => $data)),
551 theme('dna_permission', _devel_node_access_explain_access('create', $nid, $account)),
552 theme('dna_permission', _devel_node_access_explain_access('view', $nid, $account)),
553 theme('dna_permission', _devel_node_access_explain_access('update', $nid, $account)),
554 theme('dna_permission', _devel_node_access_explain_access('delete', $nid, $account)),
4749300a 555 );
556 }
557 if (count($rows)) {
1094cb03 558 $output = theme('table', array('headers' => $headers, 'rows' => $rows, 'attributes' => array('style' => 'text-align: left')));
559 $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)));
4749300a 560 return array('subject' => t('Access permissions by user'),
561 'content' => $output);
562 }
edaf11c1 563 }
b5d6be8c 564 break;
361dbb76
DC
565 }
566}
567
aa60cf2c 568/**
1094cb03 569 * Helper function that mimicks node.module's node_access() function.
570 *
571 * Unfortunately, this needs to be updated manually whenever node.module changes!
572 *
573 * @return
574 * An array suitable for theming with theme_dna_permission().
575 */
576function _devel_node_access_explain_access($op, $node, $account = NULL) {
577 global $user;
578
579 if (is_numeric($node) && !($node = node_load($node))) {
580 return array( FALSE, '???',
581 t('Unable to load the node &ndash; this should never happen!'),
582 );
583 }
584 if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
585 return array( FALSE, t('!NO: invalid $op', array('!NO' => t('NO'))),
586 t("'@op' is an invalid operation!", array('@op' => $op)),
587 );
588 }
589
590 if ($op == 'create' && is_object($node)) {
591 $node = $node->type;
592 }
593
594 if (!empty($account)) {
595 // To try to get the most authentic result we impersonate the given user!
596 // This may reveal bugs in other modules, leading to contradictory results.
597 $saved_user = $user;
598 drupal_save_session(FALSE);
599 $user = $account;
600 $result = _devel_node_access_explain_access($op, $node, NULL);
601 $user = $saved_user;
602 drupal_save_session(TRUE);
603 $second_opinion = node_access($op, $node, $account);
604 if ($second_opinion != $result[0]) {
605 $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>';
606 }
607 return $result;
608 }
609
610 $variables = array(
611 '!NO' => t('NO'),
612 '!YES' => t('YES'),
613 );
614
615 if (user_access('bypass node access')) {
616 return array( TRUE,
617 t('!YES: bypass node access', $variables),
618 t("!YES: This user has the '!bypass_node_access' permission and may do everything with nodes.", $variables += array(
619 '!bypass_node_access' => t('bypass node access'),
620 )),
621 );
622 }
623
624 if (!user_access('access content')) {
625 return array( FALSE,
626 t('!NO: access content', $variables),
627 t("!NO: This user does not have the '!access_content' permission and is denied doing anything with content.", $variables += array(
628 '!access_content' => t('access content'),
629 )),
630 );
631 }
632
633 foreach (module_implements('node_access') as $module) {
634 $function = $module . '_node_access';
635 if (function_exists($function)) {
636 $result = $function($node, $op, $user);
637 if ($module == 'node') {
638 $module = 'node (permissions)';
639 }
640 if (isset($result)) {
641 if ($result === NODE_ACCESS_DENY) {
642 $denied_by[] = $module;
643 }
644 elseif ($result === NODE_ACCESS_ALLOW) {
645 $allowed_by[] = $module;
646 }
647 $access[] = $result;
648 }
649 }
650 }
651 $variables += array(
652 '@deniers' => (empty($denied_by) ? NULL : implode(', ', $denied_by)),
653 '@allowers' => (empty($allowed_by) ? NULL : implode(', ', $allowed_by)),
654 );
655 if (!empty($denied_by)) {
656 $variables += array(
657 '%module' => $denied_by[0],
658 );
659 return array( FALSE,
660 t('!NO: by %module', $variables),
661 (empty($allowed_by)
662 ? t("!NO: hook_node_access() of the following module(s) denies this: @deniers.", $variables)
663 : t("!NO: hook_node_access() of the following module(s) denies this: @deniers &ndash; even though the following module(s) would allow it: @allowers.", $variables)),
664 );
665 }
666 if (!empty($allowed_by)) {
667 $variables += array(
668 '%module' => $allowed_by[0],
669 );
670 return array( TRUE,
671 t('!YES: by %module', $variables),
672 t("!YES: hook_node_access() of the following module(s) allows this: @allowers.", $variables),
673 );
674 }
675
676 if ($op == 'view' && !$node->status && user_access('view own unpublished content') && $user->uid == $node->uid && $user->uid != 0) {
677 return array( TRUE,
678 t('!YES: view own unpublished content', $variables),
679 t("!YES: The node is unpublished, but the user has the '!view_own_unpublished_content' permission.", $variables += array(
680 '!view_own_unpublished_content' => t('view own unpublished content'),
681 )),
682 );
683 }
684
685 if ($op != 'create' && $node->nid) {
686 if (node_access($op, $node)) { // delegate this part
687 return array( TRUE,
688 t('!YES: node access', $variables),
689 t('!YES: Node access allows this.', $variables),
690 );
691 }
692 else {
693 return array( FALSE,
694 t('!NO: node access', $variables),
695 t('!NO: Node access denies this.', $variables),
696 );
697 }
698 }
699
700 return array( FALSE,
701 t('!NO: no reason', $variables),
702 t("!NO: None of the checks resulted in allowing this, so it's denied.", $variables) .
703 ($op == 'create' ? ' ' . t('This is most likely due to a withheld permission.') : ''),
704 );
705}
706
707/**
708 * Helper function to create a list of the grants returned by hook_node_grants().
709 */
710function _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) {
711 if (!empty($checked_status[$nid])) {
712 $cgs_by_realm = array();
713 foreach ($checked_grants[$nid]['view'] as $realm => $cg) {
714 if (isset($cg['#module'])) {
715 $module = $cg['#module'];
716 unset($cg['#module']);
717 if (!empty($module) && (strpos($realm, $module) !== 0)) {
718 $realm = $module . ':' . $realm;
719 }
720 }
721 $cgs_by_realm[$realm] = $realm . ': ' . implode(', ', $cg);
722 }
723 if (!empty($cgs_by_realm)) {
724 return theme('item_list', array('items' => array_values($cgs_by_realm), 'type' => 'ul'));
725 }
726 }
727}
728
729/**
09344f2f 730 * Implementation of hook_node_access_explain().
edaf11c1 731 */
aa60cf2c 732function devel_node_access_node_access_explain($row) {
fec1eb09 733 if ($row->gid == 0 && $row->realm == 'all') {
a2d2ff9f 734 foreach (array('view', 'update', 'delete') as $op) {
1094cb03 735 $gop = 'grant_' . $op;
a2d2ff9f 736 if (!empty($row->$gop)) {
737 $ops[] = $op;
738 }
739 }
740 if (empty($ops)) {
1094cb03 741 return '(No access granted to ' . ($row->nid == 0 ? 'any nodes.)' : 'this node.)');
d73b19bd 742 }
edaf11c1 743 else {
1094cb03 744 return 'All users may ' . implode('/', $ops) . ($row->nid == 0 ? ' all nodes.' : ' this node.');
fec1eb09 745 }
aa60cf2c 746 }
747}
361dbb76 748
44404c01 749/**
1094cb03 750 * Helper function to return a sanitized node title.
751 */
752function _devel_node_access_get_node_title($node, $clip_and_decorate = TRUE) {
753 if (isset($node)) {
2f19ebc0 754 if (isset($node->title[FIELD_LANGUAGE_NONE][0]['value'])) {
755 $node_title = check_plain($node->title[FIELD_LANGUAGE_NONE][0]['value']);
1094cb03 756 if ($clip_and_decorate) {
757 if (drupal_strlen($node_title) > 20) {
758 $node_title = "<span title='node/$node->nid: $node_title'>" . drupal_substr($node_title, 0, 15) . '...</span>';
759 }
760 $node_title = '<span title="node/' . $node->nid . '">' . $node_title . '</span>';
761 }
762 return $node_title;
763 }
764 elseif (isset($node->nid)) {
765 return $node->nid;
766 }
767 }
768 return '&mdash;';
769}
770
771/**
09344f2f 772 * Implementation of hook_theme().
44404c01 773 */
774function devel_node_access_theme() {
775 return array(
776 'dna_permission' => array(
09344f2f 777 'arguments' => array('permission' => NULL),
44404c01 778 ),
779 );
780}
781
782/**
783 * Indicate whether user has a permission or not.
44404c01 784 */
785function theme_dna_permission($permission) {
1094cb03 786 return '<span class="' . ($permission[0] ? 'ok' : 'error') . '" title="' . $permission[2] . '">' . $permission[1] . '</span>';
44404c01 787}