/[drupal]/contributions/modules/devel/devel_node_access.module
ViewVC logotype

Contents of /contributions/modules/devel/devel_node_access.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.41 - (show annotations) (download) (as text)
Sat Nov 7 22:33:21 2009 UTC (3 weeks ago) by salvis
Branch: MAIN
CVS Tags: HEAD
Changes since 1.40: +3 -2 lines
File MIME type: text/x-php
Be specific about showing only the 'view' grants returned from hook_node_grants().
1 <?php
2 // $Id: devel_node_access.module,v 1.40 2009/11/07 21:43:12 salvis Exp $
3 /**
4 * @file
5 *
6 * This module gives developers feedback as to what their
7 * node_access table contains, and which nodes are protected or
8 * visible to the public.
9 *
10 */
11
12 define('DNA_ACCESS_VIEW', 'view devel_node_access information');
13
14 function 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 );
21 }
22
23 /**
24 * Implementation of hook_help().
25 */
26 function 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':
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.',
37 array('@settings_page' => url('admin/config/development/devel', array('fragment' => 'edit-devel-node-access-debug-mode')))
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.',
40 array('@summary_page' => url('devel/node_access/summary'))
41 ) . "</p>\n";
42 return $output;
43 }
44 }
45
46 function devel_node_access_menu() {
47 $items = array();
48
49 // Add this to the custom menu 'devel' created by devel module.
50 $items['devel/node_access/summary'] = array(
51 'title' => 'Node_access summary',
52 'page callback' => 'dna_summary',
53 'access arguments' => array(DNA_ACCESS_VIEW),
54 'menu_name' => 'devel',
55 );
56
57 if (!module_exists('devel')) {
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
66 $items['admin/config/development/devel'] = array(
67 'title' => 'Devel settings',
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.',
69 'page callback' => 'drupal_get_form',
70 'page arguments' => array('devel_node_access_admin_settings'),
71 'access arguments' => array('administer site configuration'),
72 'menu_name' => 'devel',
73 );
74 }
75
76 return $items;
77 }
78
79 function devel_node_access_admin_settings() {
80 $form = array();
81 return system_settings_form($form);
82 }
83
84 function devel_node_access_form_alter(&$form, $form_state, $form_id) {
85 $tr = 't';
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),
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'))),
92 );
93 // push these down:
94 $form['buttons']['#weight'] = 1;
95 }
96 }
97
98 function dna_summary() {
99 // Warn user if they have any entries that could grant access to all nodes
100 $output = '';
101 $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid = 0');
102 $rows = array();
103 foreach ($result as $row) {
104 $rows[] = array($row->realm);
105 }
106 if (!empty($rows)) {
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";
109 $headers = array(t('realm'));
110 $output .= theme('table', array('header' => $headers, 'rows' => $rows));
111 $access_granted_to_all_nodes = TRUE;
112 }
113
114 // how many nodes are not represented in the node_access table
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>' .
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')))
120 . "</p>\n";
121 if (!empty($access_granted_to_all_nodes)) {
122 $output .= '<p>' .
123 t('This issue may be masked by the one above, so look into the former first.')
124 . "</p>\n";
125 }
126 }
127 else {
128 $output .= '<h3>' . t('All Nodes Represented') . "</h3>\n";
129 $output .= '<p>' . t('All nodes are represented in the node_access table.') . "</p>\n";
130 }
131
132
133 // a similar warning to the one above, but slightly more specific
134 $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid <> 0');
135 $rows = array();
136 foreach ($result as $row) {
137 $rows[] = array($row->realm);
138 }
139 if (!empty($rows)) {
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";
142 $headers = array(t('realm'));
143 $output .= theme('table', array('header' => $headers, 'rows' => $rows));
144 }
145
146
147 // find specific nodes which may be visible to all users
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');
149 $rows = array();
150 foreach ($result as $row) {
151 $rows[] = array($row->realm,
152 array('data' => $row->node_count,
153 'align' => 'center'));
154 }
155 if (!empty($rows)) {
156 $output .= '<h3>' . t('Access Granted to Some Nodes') . "</h3>\n";
157 $output .= '<p>' .
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.')
159 . "</p>\n";
160 $headers = array(t('realm'), t('public nodes'));
161 $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Public Nodes')));
162 }
163
164
165 // find specific nodes protected by node_access table
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');
167 $rows = array();
168 foreach ($result as $row) {
169 // no Views yet:
170 //$rows[] = array(l($row->realm, "devel/node_access/view/$row->realm"),
171 $rows[] = array($row->realm,
172 array('data' => $row->node_count,
173 'align' => 'center'));
174 }
175 if (!empty($rows)) {
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";
178 $headers = array(t('realm'), t('private nodes'));
179 $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Protected Nodes')));
180 }
181
182 return $output;
183 }
184
185 function dna_visible_nodes($nid = NULL) {
186 static $nids = array();
187 if ($nid) {
188 $nids[$nid] = $nid;
189 }
190 return $nids;
191 }
192
193 function devel_node_access_node_view($node, $build_mode) {
194 // remember this node, for display in our block
195 dna_visible_nodes($node->nid);
196 }
197
198 function _devel_node_access_module_invoke_all() { // array and scalar returns
199 $args = func_get_args();
200 $hook = $args[0];
201 unset($args[0]);
202 $return = array();
203 foreach (module_implements($hook) as $module) {
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);
219 }
220 }
221 }
222 return $return;
223 }
224
225 function 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 );
237 return $blocks;
238 }
239
240 function devel_node_access_block_view($delta) {
241 global $user;
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
251 if (!user_access(DNA_ACCESS_VIEW)) {
252 return;
253 }
254 switch ($delta) {
255 case 'dna_node':
256 if (!count(dna_visible_nodes())) {
257 return;
258 }
259
260 // include rows where nid == 0
261 $nids = array_merge(array(0 => 0), dna_visible_nodes());
262 $query = db_select('node_access', 'na');
263 $query
264 ->fields('na')
265 ->condition('na.nid', $nids, 'IN')
266 ->orderBy('na.nid')
267 ->orderBy('na.realm')
268 ->orderBy('na.gid');
269 $nodes = node_load_multiple($nids);
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();
274 foreach ($query->execute() as $row) {
275 $explained = module_invoke_all('node_access_explain', $row);
276 $rows[] = array(_devel_node_access_get_node_title($nodes[$row->nid]),
277 $row->realm,
278 $row->gid,
279 $row->grant_view,
280 $row->grant_update,
281 $row->grant_delete,
282 implode('<br />', $explained));
283 }
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;
286 }
287 else {
288 $tr = 't';
289 $variables = array('!na' => '{node_access}');
290 $states = array(
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)),
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!')),
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)),
300 );
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 );
308 $active_grants = array();
309 foreach ($query->execute() as $active_grant) {
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();
315 if ($node = node_load($nid)) {
316 // check node_access_acquire_grants()
317 $grants = _devel_node_access_module_invoke_all('node_access_records', $node);
318 drupal_alter('node_access_records', $grants, $node);
319 if (!empty($grants)) {
320 $top_priority = NULL;
321 foreach ($grants as $grant) {
322 $priority = intval($grant['priority']);
323 $top_priority = (isset($top_priority) ? max($top_priority, $priority) : $priority);
324 $grant['priority'] = (isset($grant['priority']) ? $priority : '&ndash;&nbsp;');
325 $acquired_grants_nid[$priority][$grant['realm']][$grant['gid']] = $grant + array(
326 '#title' => _devel_node_access_get_node_title($node),
327 '#module' => (isset($grant['#module']) ? $grant['#module'] : ''),
328 );
329 }
330 krsort($acquired_grants_nid);
331 }
332 // check node_access_grants()
333 $checked_status[$nid] = $node->status;
334 if ($node->nid && $node->status) {
335 foreach (array('view', 'update', 'delete') as $op) {
336 $grants = _devel_node_access_module_invoke_all('node_grants', $user, $op);
337 drupal_alter('node_grants', $grants, $user, $op);
338 $checked_grants[$nid][$op] = array_merge(array('all' => array(0)), $grants);
339 }
340 }
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;
351 }
352 }
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']));
357 }
358 }
359 }
360 $fixed_grant = (array) $active_grant;
361 if ($count_nonempty_grants == 0 && $realm == 'all' && $gid == 0 ) {
362 $fixed_grant += array(
363 'priority' => '&ndash;',
364 'state' => 'default',
365 );
366 }
367 elseif (!$found) {
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 }
384 }
385 else {
386 continue;
387 }
388 $fixed_grant += array(
389 'nid' => $nid,
390 '#title' => _devel_node_access_get_node_title($node, FALSE),
391 );
392 $all_grants[] = $fixed_grant;
393 }
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"];
413 }
414 }
415 }
416 }
417 }
418 else {
419 $acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'illegitimate' : 'ignored');
420 }
421 $all_grants[] = $acquired_grant + array('nid' => $nid);
422 }
423 }
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'];
440 $row->explained = implode('<br />', module_invoke_all('node_access_explain', $row));
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.');
456 }
457 if (isset($grant["grant_$op!"])) {
458 $row["grant_$op"]['data'] = $grant["grant_$op!"] . '&gt;' . $row["grant_$op"]['data'];
459 $row["grant_$op"]['class'][] = 'error';
460 }
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'];
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));
468 }
469 $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('class' => array('system-status-report'), 'style' => 'text-align: left;')));
470
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)));
472
473 if ($error_count > 0) {
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)));
476 }
477
478 // Explain whether access is granted or denied, and why (using code from node_access()).
479 $tr = 't';
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;';
490 }
491 else {
492 $variables['!list'] = '<div style="margin-left: 2em">' . _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) . '</div>';
493 $variables['%access'] = 'view';
494 $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";
495 $accounts[] = $user;
496 }
497 if (arg(0) == 'node' && is_numeric(arg(1)) && !$block1_visible) { // only for single nodes
498 if (user_is_logged_in()) {
499 $accounts[] = user_load(0); // Anonymous, too
500 }
501 foreach ($accounts as $account) {
502 $variables['!username'] = theme('username', array('account' => $account));
503 $output .= "\n<div style='text-align: left'>" . t("!username has the following access", $variables) . ' ';
504 $nid_items = array();
505 foreach ($nids as $nid) {
506 $op_items = array();
507 foreach (array('create', 'view', 'update', 'delete') as $op) {
508 $explain = _devel_node_access_explain_access($op, $nid, $account);
509 $op_items[] = "<div style='width: 5em; display: inline-block'>" . t('%op:', array('%op' => $op)) . ' </div>' . $explain[2];
510 }
511 $nid_items[] = t('to node !nid:', array('!nid' => l($nid, 'node/' . $nid)))
512 . "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $op_items, 'type' => 'ul')) . '</div>';
513 }
514 if (count($nid_items) == 1) {
515 $output .= $nid_items[0];
516 }
517 else {
518 $output .= "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $nid_items, 'type' => 'ul')) . '</div>';
519 }
520 $output .= "\n</div>\n";
521 }
522 }
523 }
524
525 if (!empty($hint)) {
526 $output .= theme_form_element(array('element' => array('#value' => '', '#description' => '(' . $hint . ')', '#children' => NULL)));
527 }
528 $subject = t('node_access entries for nodes shown on this page');
529 return array('subject' => $subject, 'content' => $output . '<br /><br />');
530
531 case 'dna_user':
532 // show which users can access this node
533 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == null) {
534 $nid = arg(1);
535 $node = node_load($nid);
536 $node_type = node_type_get_type($node);
537 $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'));
538 $rows = array();
539 // Find all users. The following operations are very inefficient, so we
540 // limit the number of users returned. It would be better to make a
541 // pager query, or at least make the number of users configurable. If
542 // anyone is up for that please submit a patch.
543 $query = db_select('users', 'u');
544 $query
545 ->distinct()
546 ->fields('u')
547 ->orderBy('access', 'DESC')
548 ->range(0, 10);
549 foreach ($query->execute() as $data) {
550 $account = user_load($data->uid);
551 $rows[] = array(theme('username', array('account' => $data)),
552 theme('dna_permission', _devel_node_access_explain_access('create', $nid, $account)),
553 theme('dna_permission', _devel_node_access_explain_access('view', $nid, $account)),
554 theme('dna_permission', _devel_node_access_explain_access('update', $nid, $account)),
555 theme('dna_permission', _devel_node_access_explain_access('delete', $nid, $account)),
556 );
557 }
558 if (count($rows)) {
559 $output = theme('table', array('headers' => $headers, 'rows' => $rows, 'attributes' => array('style' => 'text-align: left')));
560 $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)));
561 return array('subject' => t('Access permissions by user'),
562 'content' => $output);
563 }
564 }
565 break;
566 }
567 }
568
569 /**
570 * Helper function that mimicks node.module's node_access() function.
571 *
572 * Unfortunately, this needs to be updated manually whenever node.module changes!
573 *
574 * @return
575 * An array suitable for theming with theme_dna_permission().
576 */
577 function _devel_node_access_explain_access($op, $node, $account = NULL) {
578 global $user;
579
580 if (is_numeric($node) && !($node = node_load($node))) {
581 return array( FALSE, '???',
582 t('Unable to load the node &ndash; this should never happen!'),
583 );
584 }
585 if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
586 return array( FALSE, t('!NO: invalid $op', array('!NO' => t('NO'))),
587 t("'@op' is an invalid operation!", array('@op' => $op)),
588 );
589 }
590
591 if ($op == 'create' && is_object($node)) {
592 $node = $node->type;
593 }
594
595 if (!empty($account)) {
596 // To try to get the most authentic result we impersonate the given user!
597 // This may reveal bugs in other modules, leading to contradictory results.
598 $saved_user = $user;
599 drupal_save_session(FALSE);
600 $user = $account;
601 $result = _devel_node_access_explain_access($op, $node, NULL);
602 $user = $saved_user;
603 drupal_save_session(TRUE);
604 $second_opinion = node_access($op, $node, $account);
605 if ($second_opinion != $result[0]) {
606 $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>';
607 }
608 return $result;
609 }
610
611 $variables = array(
612 '!NO' => t('NO'),
613 '!YES' => t('YES'),
614 );
615
616 if (user_access('bypass node access')) {
617 return array( TRUE,
618 t('!YES: bypass node access', $variables),
619 t("!YES: This user has the '!bypass_node_access' permission and may do everything with nodes.", $variables += array(
620 '!bypass_node_access' => t('bypass node access'),
621 )),
622 );
623 }
624
625 if (!user_access('access content')) {
626 return array( FALSE,
627 t('!NO: access content', $variables),
628 t("!NO: This user does not have the '!access_content' permission and is denied doing anything with content.", $variables += array(
629 '!access_content' => t('access content'),
630 )),
631 );
632 }
633
634 foreach (module_implements('node_access') as $module) {
635 $function = $module . '_node_access';
636 if (function_exists($function)) {
637 $result = $function($node, $op, $user);
638 if ($module == 'node') {
639 $module = 'node (permissions)';
640 }
641 if (isset($result)) {
642 if ($result === NODE_ACCESS_DENY) {
643 $denied_by[] = $module;
644 }
645 elseif ($result === NODE_ACCESS_ALLOW) {
646 $allowed_by[] = $module;
647 }
648 $access[] = $result;
649 }
650 }
651 }
652 $variables += array(
653 '@deniers' => (empty($denied_by) ? NULL : implode(', ', $denied_by)),
654 '@allowers' => (empty($allowed_by) ? NULL : implode(', ', $allowed_by)),
655 );
656 if (!empty($denied_by)) {
657 $variables += array(
658 '%module' => $denied_by[0],
659 );
660 return array( FALSE,
661 t('!NO: by %module', $variables),
662 (empty($allowed_by)
663 ? t("!NO: hook_node_access() of the following module(s) denies this: @deniers.", $variables)
664 : 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)),
665 );
666 }
667 if (!empty($allowed_by)) {
668 $variables += array(
669 '%module' => $allowed_by[0],
670 );
671 return array( TRUE,
672 t('!YES: by %module', $variables),
673 t("!YES: hook_node_access() of the following module(s) allows this: @allowers.", $variables),
674 );
675 }
676
677 if ($op == 'view' && !$node->status && user_access('view own unpublished content') && $user->uid == $node->uid && $user->uid != 0) {
678 return array( TRUE,
679 t('!YES: view own unpublished content', $variables),
680 t("!YES: The node is unpublished, but the user has the '!view_own_unpublished_content' permission.", $variables += array(
681 '!view_own_unpublished_content' => t('view own unpublished content'),
682 )),
683 );
684 }
685
686 if ($op != 'create' && $node->nid) {
687 if (node_access($op, $node)) { // delegate this part
688 return array( TRUE,
689 t('!YES: node access', $variables),
690 t('!YES: Node access allows this.', $variables),
691 );
692 }
693 else {
694 return array( FALSE,
695 t('!NO: node access', $variables),
696 t('!NO: Node access denies this.', $variables),
697 );
698 }
699 }
700
701 return array( FALSE,
702 t('!NO: no reason', $variables),
703 t("!NO: None of the checks resulted in allowing this, so it's denied.", $variables) .
704 ($op == 'create' ? ' ' . t('This is most likely due to a withheld permission.') : ''),
705 );
706 }
707
708 /**
709 * Helper function to create a list of the grants returned by hook_node_grants().
710 */
711 function _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) {
712 if (!empty($checked_status[$nid])) {
713 $cgs_by_realm = array();
714 foreach ($checked_grants[$nid]['view'] as $realm => $cg) {
715 if (isset($cg['#module'])) {
716 $module = $cg['#module'];
717 unset($cg['#module']);
718 if (!empty($module) && (strpos($realm, $module) !== 0)) {
719 $realm = $module . ':' . $realm;
720 }
721 }
722 $cgs_by_realm[$realm] = $realm . ': ' . implode(', ', $cg);
723 }
724 if (!empty($cgs_by_realm)) {
725 return theme('item_list', array('items' => array_values($cgs_by_realm), 'type' => 'ul'));
726 }
727 }
728 }
729
730 /**
731 * Implementation of hook_node_access_explain().
732 */
733 function devel_node_access_node_access_explain($row) {
734 if ($row->gid == 0 && $row->realm == 'all') {
735 foreach (array('view', 'update', 'delete') as $op) {
736 $gop = 'grant_' . $op;
737 if (!empty($row->$gop)) {
738 $ops[] = $op;
739 }
740 }
741 if (empty($ops)) {
742 return '(No access granted to ' . ($row->nid == 0 ? 'any nodes.)' : 'this node.)');
743 }
744 else {
745 return 'All users may ' . implode('/', $ops) . ($row->nid == 0 ? ' all nodes.' : ' this node.');
746 }
747 }
748 }
749
750 /**
751 * Helper function to return a sanitized node title.
752 */
753 function _devel_node_access_get_node_title($node, $clip_and_decorate = TRUE) {
754 if (isset($node)) {
755 if (isset($node->title[FIELD_LANGUAGE_NONE][0]['value'])) {
756 $node_title = check_plain($node->title[FIELD_LANGUAGE_NONE][0]['value']);
757 if ($clip_and_decorate) {
758 if (drupal_strlen($node_title) > 20) {
759 $node_title = "<span title='node/$node->nid: $node_title'>" . drupal_substr($node_title, 0, 15) . '...</span>';
760 }
761 $node_title = '<span title="node/' . $node->nid . '">' . $node_title . '</span>';
762 }
763 return $node_title;
764 }
765 elseif (isset($node->nid)) {
766 return $node->nid;
767 }
768 }
769 return '&mdash;';
770 }
771
772 /**
773 * Implementation of hook_theme().
774 */
775 function devel_node_access_theme() {
776 return array(
777 'dna_permission' => array(
778 'arguments' => array('permission' => NULL),
779 ),
780 );
781 }
782
783 /**
784 * Indicate whether user has a permission or not.
785 */
786 function theme_dna_permission($permission) {
787 return '<span class="' . ($permission[0] ? 'ok' : 'error') . '" title="' . $permission[2] . '">' . $permission[1] . '</span>';
788 }

  ViewVC Help
Powered by ViewVC 1.1.2