/[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.36 - (show annotations) (download) (as text)
Wed Oct 14 01:28:19 2009 UTC (6 weeks, 4 days ago) by salvis
Branch: MAIN
Changes since 1.35: +378 -143 lines
File MIME type: text/x-php
Port to D7 #529772 as well as all other updates that went into the D6 version in the meantime; adapt to core API changes.
1 <?php
2 // $Id: devel_node_access.module,v 1.34 2009/09/13 12:11:29 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->leftJoin('node', 'n', 'n.nid = na.nid');
264 $query
265 ->fields('na')
266 ->fields('n', array('title'))
267 ->condition('na.nid', $nids, 'IN')
268 ->orderBy('na.nid')
269 ->orderBy('na.realm')
270 ->orderBy('na.gid');
271
272 if (!variable_get('devel_node_access_debug_mode', FALSE)) {
273 $headers = array(t('node'), t('realm'), t('gid'), t('view'), t('update'), t('delete'), t('explained'));
274 $rows = array();
275 foreach ($query->execute() as $row) {
276 $explained = module_invoke_all('node_access_explain', $row);
277 $rows[] = array(_devel_node_access_get_node_title($row),
278 $row->realm,
279 $row->gid,
280 $row->grant_view,
281 $row->grant_update,
282 $row->grant_delete,
283 implode('<br />', $explained));
284 }
285 $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('style' => 'text-align: left')));
286 $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;
287 }
288 else {
289 $tr = 't';
290 $variables = array('!na' => '{node_access}');
291 $states = array(
292 'default' => array(t('default'), 'ok', t('Default grant supplied by core in the absence of any other non-empty grants, in !na.', $variables)),
293 'ok' => array(t('ok'), 'ok', t('Highest priority grant, in !na.', $variables)),
294 'static' => array(t('static'), 'ok', t('Non-standard grant in !na.', $variables)),
295 '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!')),
296 'ignored' => array(t('ignored'), 'warning', t('Lower priority grant, not in !na and thus ignored.', $variables)),
297 'empty' => array(t('empty'), 'warning', t('Does not grant any access, but could block lower priority grants; not in !na.', $variables)),
298 'missing' => array(t('missing'), 'error', t("Should be in !na but isn't!", $variables)),
299 'illegitimate' => array(t('illegitimate'), 'error', t('Should NOT be in !na because of lower priority!', $variables)),
300 'alien' => array(t('alien'), 'error', t('Should NOT be in !na because of unknown origin!', $variables)),
301 );
302 $headers = array(t('node'), t('prio'), t('status'), t('realm'), t('gid'), t('view'), t('update'), t('delete'), t('explained'));
303 $active_grants = array();
304 foreach ($query->execute() as $active_grant) {
305 $active_grants[$active_grant->nid][$active_grant->realm][$active_grant->gid] = $active_grant;
306 }
307 $all_grants = $checked_grants = $checked_status = array();
308 foreach ($nids as $nid) {
309 $acquired_grants_nid = array();
310 if ($node = node_load($nid)) {
311 // check node_access_acquire_grants()
312 $grants = _devel_node_access_module_invoke_all('node_access_records', $node);
313 if (!empty($grants)) {
314 $top_priority = NULL;
315 foreach ($grants as $grant) {
316 $priority = intval($grant['priority']);
317 $top_priority = (isset($top_priority) ? max($top_priority, $priority) : $priority);
318 $grant['priority'] = (isset($grant['priority']) ? $priority : '&ndash;&nbsp;');
319 $acquired_grants_nid[$priority][$grant['realm']][$grant['gid']] = $grant + array(
320 '#title' => _devel_node_access_get_node_title($node),
321 '#module' => (isset($grant['#module']) ? $grant['#module'] : ''),
322 );
323 }
324 krsort($acquired_grants_nid);
325 }
326 // check node_access_grants()
327 $checked_status[$nid] = $node->status;
328 if ($node->nid && $node->status) {
329 foreach (array('view', 'update', 'delete') as $op) {
330 $checked_grants[$nid][$op] = array_merge(
331 array('all' => array(0)),
332 _devel_node_access_module_invoke_all('node_grants', $user, $op)
333 );
334 }
335 }
336 }
337 // check for grants in the node_access table that aren't returned by node_access_acquire_grants()
338 $found = FALSE;
339 if (isset($active_grants[$nid])) {
340 foreach ($active_grants[$nid] as $realm => $active_grants_realm) {
341 foreach ($active_grants_realm as $gid => $active_grant) {
342 $count_nonempty_grants = 0;
343 foreach ($acquired_grants_nid as $priority => $acquired_grants_nid_priority) {
344 if (isset($acquired_grants_nid_priority[$realm][$gid])) {
345 $found = TRUE;
346 }
347 }
348 if ($acquired_grants_nid_priority = reset($acquired_grants_nid)) { // highest priority only
349 foreach ($acquired_grants_nid_priority as $acquired_grants_nid_priority_realm) {
350 foreach ($acquired_grants_nid_priority_realm as $acquired_grants_nid_priority_realm_gid) {
351 $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']));
352 }
353 }
354 }
355 $fixed_grant = (array) $active_grant;
356 if ($count_nonempty_grants == 0 && $realm == 'all' && $gid == 0 ) {
357 $fixed_grant += array(
358 'priority' => '&ndash;',
359 'state' => 'default',
360 );
361 }
362 elseif (!$found) {
363 $acknowledged = _devel_node_access_module_invoke_all('node_access_acknowledge', $fixed_grant);
364 if (empty($acknowledged)) {
365 // no one acknowledged this record, mark it as alien:
366 $fixed_grant += array(
367 'priority' => '?',
368 'state' => 'alien',
369 );
370 }
371 else {
372 // at least one module acknowledged the record, attribute it to the first one:
373 $fixed_grant += array(
374 'priority' => '&ndash;',
375 'state' => 'static',
376 '#module' => reset(array_keys($acknowledged)),
377 );
378 }
379 }
380 else {
381 continue;
382 }
383 $fixed_grant += array(
384 'nid' => $nid,
385 '#title' => _devel_node_access_get_node_title($node, FALSE),
386 );
387 $all_grants[] = $fixed_grant;
388 }
389 }
390 }
391 // order grants and evaluate their status
392 foreach ($acquired_grants_nid as $priority => $acquired_grants_priority) {
393 ksort($acquired_grants_priority);
394 foreach ($acquired_grants_priority as $realm => $acquired_grants_realm) {
395 ksort($acquired_grants_realm);
396 foreach ($acquired_grants_realm as $gid => $acquired_grant) {
397 if ($priority == $top_priority) {
398 if (empty($acquired_grant['grant_view']) && empty($acquired_grant['grant_update']) && empty($acquired_grant['grant_delete'])) {
399 $acquired_grant['state'] = 'empty';
400 }
401 else {
402 $acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'ok' : 'missing');
403 if ($acquired_grant['state'] == 'ok') {
404 foreach (array('view', 'update', 'delete') as $op) {
405 $active_grant = (array) $active_grants[$nid][$realm][$gid];
406 if (empty($acquired_grant["grant_$op"]) != empty($active_grant["grant_$op"]) ) {
407 $acquired_grant["grant_$op!"] = $active_grant["grant_$op"];
408 }
409 }
410 }
411 }
412 }
413 else {
414 $acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'illegitimate' : 'ignored');
415 }
416 $all_grants[] = $acquired_grant + array('nid' => $nid);
417 }
418 }
419 }
420 }
421 // fill in the table rows
422 $rows = array();
423 $error_count = 0;
424 foreach ($all_grants as $grant) {
425 $row = new stdClass();
426 $row->nid = $grant['nid'];
427 $row->title = $grant['#title'];
428 $row->priority = $grant['priority'];
429 $row->state = array('data' => $states[$grant['state']][0], 'title' => $states[$grant['state']][2]);
430 $row->realm = $grant['realm'];
431 $row->gid = $grant['gid'];
432 $row->grant_view = $grant['grant_view'];
433 $row->grant_update = $grant['grant_update'];
434 $row->grant_delete = $grant['grant_delete'];
435 $row->explained = implode('<br />', module_invoke_all('node_access_explain', $row));
436 unset($row->title);
437 if ($row->nid == 0 && $row->gid == 0 && $row->realm == 'all' && count($all_grants) > 1) {
438 $row->state = array('data' => $states['unexpected'][0], 'title' => $states['unexpected'][2]);
439 $class = $states['unexpected'][1];
440 }
441 else {
442 $class = $states[$grant['state']][1];
443 }
444 $error_count += ($class == 'error');
445 $row = (array) $row;
446 foreach (array('view', 'update', 'delete') as $op) {
447 $row["grant_$op"] = array('data' => $row["grant_$op"]);
448 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'])) {
449 $row["grant_$op"]['data'] .= '&prime;';
450 $row["grant_$op"]['title'] = t('This entry grants access to this node to this user.');
451 }
452 if (isset($grant["grant_$op!"])) {
453 $row["grant_$op"]['data'] = $grant["grant_$op!"] . '&gt;' . $row["grant_$op"]['data'];
454 $row["grant_$op"]['class'][] = 'error';
455 }
456 }
457 foreach (array('nid', 'priority', 'gid') as $key) {
458 $row[$key] = array('data' => $row[$key], 'style' => 'text-align: right');
459 }
460 $row['nid']['title'] = $grant['#title'];
461 $row['realm'] = (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0 ? '' : $grant['#module'] . ':<br />') . $grant['realm'];
462 $rows[] = array('data' => array_values($row), 'class' => array('even', $class));
463 }
464 $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('class' => array('system-status-report'), 'style' => 'text-align: left;')));
465
466 $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)));
467
468 if ($error_count > 0) {
469 $variables['!Rebuild_permissions'] = '<a href="' . url('admin/reports/status/rebuild') . '">' . $tr('Rebuild permissions') . '</a>';
470 $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)));
471 }
472
473 // Explain whether access is granted or denied, and why (using code from node_access()).
474 $tr = 't';
475 array_shift($nids); // remove the 0
476 $accounts = array();
477 $variables += array(
478 '!username' => theme('username', array('account' => $user)),
479 '%uid' => $user->uid,
480 );
481
482 if (user_access('bypass node access')) {
483 $variables['%bypass_node_access'] = $tr('bypass node access');
484 $output .= t('!username has the %bypass_node_access permission and thus full access to all nodes.', $variables) . '<br />&nbsp;';
485 }
486 else {
487 $variables['!list'] = '<div style="margin-left: 2em">' . _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) . '</div>';
488 $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";
489 $accounts[] = $user;
490 }
491 if (arg(0) == 'node' && is_numeric(arg(1)) && !$block1_visible) { // only for single nodes
492 if (user_is_logged_in()) {
493 $accounts[] = user_load(0); // Anonymous, too
494 }
495 foreach ($accounts as $account) {
496 $variables['!username'] = theme('username', array('account' => $account));
497 $output .= "\n<div style='text-align: left'>" . t("!username has the following access", $variables) . ' ';
498 $nid_items = array();
499 foreach ($nids as $nid) {
500 $op_items = array();
501 foreach (array('create', 'view', 'update', 'delete') as $op) {
502 $explain = _devel_node_access_explain_access($op, $nid, $account);
503 $op_items[] = "<div style='width: 5em; display: inline-block'>" . t('%op:', array('%op' => $op)) . ' </div>' . $explain[2];
504 }
505 $nid_items[] = t('to node !nid:', array('!nid' => l($nid, 'node/' . $nid)))
506 . "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $op_items, 'type' => 'ul')) . '</div>';
507 }
508 if (count($nid_items) == 1) {
509 $output .= $nid_items[0];
510 }
511 else {
512 $output .= "\n<div style='margin-left: 2em'>" . theme('item_list', array('items' => $nid_items, 'type' => 'ul')) . '</div>';
513 }
514 $output .= "\n</div>\n";
515 }
516 }
517 }
518
519 if (!empty($hint)) {
520 $output .= theme_form_element(array('element' => array('#value' => '', '#description' => '(' . $hint . ')', '#children' => NULL)));
521 }
522 $subject = t('node_access entries for nodes shown on this page');
523 return array('subject' => $subject, 'content' => $output . '<br /><br />');
524
525 case 'dna_user':
526 // show which users can access this node
527 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == null) {
528 $nid = arg(1);
529 $node = node_load($nid);
530 $node_type = node_type_get_type($node);
531 $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'));
532 $rows = array();
533 // Find all users. The following operations are very inefficient, so we
534 // limit the number of users returned. It would be better to make a
535 // pager query, or at least make the number of users configurable. If
536 // anyone is up for that please submit a patch.
537 $query = db_select('users', 'u');
538 $query
539 ->distinct()
540 ->fields('u')
541 ->orderBy('access', 'DESC')
542 ->range(0, 10);
543 foreach ($query->execute() as $data) {
544 $account = user_load($data->uid);
545 $rows[] = array(theme('username', array('account' => $data)),
546 theme('dna_permission', _devel_node_access_explain_access('create', $nid, $account)),
547 theme('dna_permission', _devel_node_access_explain_access('view', $nid, $account)),
548 theme('dna_permission', _devel_node_access_explain_access('update', $nid, $account)),
549 theme('dna_permission', _devel_node_access_explain_access('delete', $nid, $account)),
550 );
551 }
552 if (count($rows)) {
553 $output = theme('table', array('headers' => $headers, 'rows' => $rows, 'attributes' => array('style' => 'text-align: left')));
554 $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)));
555 return array('subject' => t('Access permissions by user'),
556 'content' => $output);
557 }
558 }
559 break;
560 }
561 }
562
563 /**
564 * Helper function that mimicks node.module's node_access() function.
565 *
566 * Unfortunately, this needs to be updated manually whenever node.module changes!
567 *
568 * @return
569 * An array suitable for theming with theme_dna_permission().
570 */
571 function _devel_node_access_explain_access($op, $node, $account = NULL) {
572 global $user;
573
574 if (is_numeric($node) && !($node = node_load($node))) {
575 return array( FALSE, '???',
576 t('Unable to load the node &ndash; this should never happen!'),
577 );
578 }
579 if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
580 return array( FALSE, t('!NO: invalid $op', array('!NO' => t('NO'))),
581 t("'@op' is an invalid operation!", array('@op' => $op)),
582 );
583 }
584
585 if ($op == 'create' && is_object($node)) {
586 $node = $node->type;
587 }
588
589 if (!empty($account)) {
590 // To try to get the most authentic result we impersonate the given user!
591 // This may reveal bugs in other modules, leading to contradictory results.
592 $saved_user = $user;
593 drupal_save_session(FALSE);
594 $user = $account;
595 $result = _devel_node_access_explain_access($op, $node, NULL);
596 $user = $saved_user;
597 drupal_save_session(TRUE);
598 $second_opinion = node_access($op, $node, $account);
599 if ($second_opinion != $result[0]) {
600 $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>';
601 }
602 return $result;
603 }
604
605 $variables = array(
606 '!NO' => t('NO'),
607 '!YES' => t('YES'),
608 );
609
610 if (user_access('bypass node access')) {
611 return array( TRUE,
612 t('!YES: bypass node access', $variables),
613 t("!YES: This user has the '!bypass_node_access' permission and may do everything with nodes.", $variables += array(
614 '!bypass_node_access' => t('bypass node access'),
615 )),
616 );
617 }
618
619 if (!user_access('access content')) {
620 return array( FALSE,
621 t('!NO: access content', $variables),
622 t("!NO: This user does not have the '!access_content' permission and is denied doing anything with content.", $variables += array(
623 '!access_content' => t('access content'),
624 )),
625 );
626 }
627
628 foreach (module_implements('node_access') as $module) {
629 $function = $module . '_node_access';
630 if (function_exists($function)) {
631 $result = $function($node, $op, $user);
632 if ($module == 'node') {
633 $module = 'node (permissions)';
634 }
635 if (isset($result)) {
636 if ($result === NODE_ACCESS_DENY) {
637 $denied_by[] = $module;
638 }
639 elseif ($result === NODE_ACCESS_ALLOW) {
640 $allowed_by[] = $module;
641 }
642 $access[] = $result;
643 }
644 }
645 }
646 $variables += array(
647 '@deniers' => (empty($denied_by) ? NULL : implode(', ', $denied_by)),
648 '@allowers' => (empty($allowed_by) ? NULL : implode(', ', $allowed_by)),
649 );
650 if (!empty($denied_by)) {
651 $variables += array(
652 '%module' => $denied_by[0],
653 );
654 return array( FALSE,
655 t('!NO: by %module', $variables),
656 (empty($allowed_by)
657 ? t("!NO: hook_node_access() of the following module(s) denies this: @deniers.", $variables)
658 : 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)),
659 );
660 }
661 if (!empty($allowed_by)) {
662 $variables += array(
663 '%module' => $allowed_by[0],
664 );
665 return array( TRUE,
666 t('!YES: by %module', $variables),
667 t("!YES: hook_node_access() of the following module(s) allows this: @allowers.", $variables),
668 );
669 }
670
671 if ($op == 'view' && !$node->status && user_access('view own unpublished content') && $user->uid == $node->uid && $user->uid != 0) {
672 return array( TRUE,
673 t('!YES: view own unpublished content', $variables),
674 t("!YES: The node is unpublished, but the user has the '!view_own_unpublished_content' permission.", $variables += array(
675 '!view_own_unpublished_content' => t('view own unpublished content'),
676 )),
677 );
678 }
679
680 if ($op != 'create' && $node->nid) {
681 if (node_access($op, $node)) { // delegate this part
682 return array( TRUE,
683 t('!YES: node access', $variables),
684 t('!YES: Node access allows this.', $variables),
685 );
686 }
687 else {
688 return array( FALSE,
689 t('!NO: node access', $variables),
690 t('!NO: Node access denies this.', $variables),
691 );
692 }
693 }
694
695 return array( FALSE,
696 t('!NO: no reason', $variables),
697 t("!NO: None of the checks resulted in allowing this, so it's denied.", $variables) .
698 ($op == 'create' ? ' ' . t('This is most likely due to a withheld permission.') : ''),
699 );
700 }
701
702 /**
703 * Helper function to create a list of the grants returned by hook_node_grants().
704 */
705 function _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) {
706 if (!empty($checked_status[$nid])) {
707 $cgs_by_realm = array();
708 foreach ($checked_grants[$nid]['view'] as $realm => $cg) {
709 if (isset($cg['#module'])) {
710 $module = $cg['#module'];
711 unset($cg['#module']);
712 if (!empty($module) && (strpos($realm, $module) !== 0)) {
713 $realm = $module . ':' . $realm;
714 }
715 }
716 $cgs_by_realm[$realm] = $realm . ': ' . implode(', ', $cg);
717 }
718 if (!empty($cgs_by_realm)) {
719 return theme('item_list', array('items' => array_values($cgs_by_realm), 'type' => 'ul'));
720 }
721 }
722 }
723
724 /**
725 * Implementation of hook_node_access_explain().
726 */
727 function devel_node_access_node_access_explain($row) {
728 if ($row->gid == 0 && $row->realm == 'all') {
729 foreach (array('view', 'update', 'delete') as $op) {
730 $gop = 'grant_' . $op;
731 if (!empty($row->$gop)) {
732 $ops[] = $op;
733 }
734 }
735 if (empty($ops)) {
736 return '(No access granted to ' . ($row->nid == 0 ? 'any nodes.)' : 'this node.)');
737 }
738 else {
739 return 'All users may ' . implode('/', $ops) . ($row->nid == 0 ? ' all nodes.' : ' this node.');
740 }
741 }
742 }
743
744 /**
745 * Helper function to return a sanitized node title.
746 */
747 function _devel_node_access_get_node_title($node, $clip_and_decorate = TRUE) {
748 if (isset($node)) {
749 if (isset($node->title[FIELD_LANGUAGE_NONE][0]['value'])) {
750 $node_title = check_plain($node->title[FIELD_LANGUAGE_NONE][0]['value']);
751 if ($clip_and_decorate) {
752 if (drupal_strlen($node_title) > 20) {
753 $node_title = "<span title='node/$node->nid: $node_title'>" . drupal_substr($node_title, 0, 15) . '...</span>';
754 }
755 $node_title = '<span title="node/' . $node->nid . '">' . $node_title . '</span>';
756 }
757 return $node_title;
758 }
759 elseif (isset($node->nid)) {
760 return $node->nid;
761 }
762 }
763 return '&mdash;';
764 }
765
766 /**
767 * Implementation of hook_theme().
768 */
769 function devel_node_access_theme() {
770 return array(
771 'dna_permission' => array(
772 'arguments' => array('permission' => NULL),
773 ),
774 );
775 }
776
777 /**
778 * Indicate whether user has a permission or not.
779 */
780 function theme_dna_permission($permission) {
781 return '<span class="' . ($permission[0] ? 'ok' : 'error') . '" title="' . $permission[2] . '">' . $permission[1] . '</span>';
782 }

  ViewVC Help
Powered by ViewVC 1.1.2