Issue #1071200 by danielb: Return renderable arrays rather than HTML output.
[project/devel.git] / devel_node_access.module
CommitLineData
361dbb76 1<?php
361dbb76 2/**
3c1264d3
JC
3 * @file
4 *
b5d6be8c 5 * This module gives developers feedback as to what their
361dbb76
DC
6 * node_access table contains, and which nodes are protected or
7 * visible to the public.
3c1264d3 8 *
361dbb76
DC
9 */
10
11define('DNA_ACCESS_VIEW', 'view devel_node_access information');
12
1094cb03 13function devel_node_access_permission() {
14 return array(
15 'view devel_node_access information' => array(
16 'description' => t('View the node access information blocks on node pages and the summary page.'),
969760e9 17 'title' => t('Access DNA information'),
e3d5f819 18 'restrict access' => TRUE,
1094cb03 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.',
969760e9 37 array('@settings_page' => url('admin/config/development/devel', array('fragment' => 'edit-devel-node-access')))
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
f72fc647 49 if (!module_exists('devel')) {
dc352d9d 50 if (!menu_load('devel')) {
51 // we have to create the 'devel' menu ourselves
52 $menu = array(
53 'menu_name' => 'devel',
54 'title' => 'Development',
55 'description' => 'Development link',
56 );
57 menu_save($menu);
58 }
1094cb03 59
dc352d9d 60 // we have to create the 'Devel settings' menu item ourselves
90bc9415 61 $items['admin/config/development/devel'] = array(
969760e9 62 'title' => 'Devel settings',
63 '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.',
64 'page callback' => 'drupal_get_form',
65 'page arguments' => array('devel_node_access_admin_settings'),
f72fc647 66 'access arguments' => array('administer site configuration'),
f72fc647 67 );
dc352d9d 68 $items['devel/settings'] = $items['admin/config/development/devel'] + array(
69 'menu_name' => 'devel',
70 );
f72fc647 71 }
72
dc352d9d 73 // add this to the custom menu 'devel' created by the devel module.
74 $items['devel/node_access/summary'] = array(
75 'title' => 'Node_access summary',
76 'page callback' => 'dna_summary',
77 'access arguments' => array(DNA_ACCESS_VIEW),
78 'menu_name' => 'devel',
79 );
80
361dbb76
DC
81 return $items;
82}
83
f72fc647 84function devel_node_access_admin_settings() {
85 $form = array();
86 return system_settings_form($form);
87}
88
89function devel_node_access_form_alter(&$form, $form_state, $form_id) {
17507063 90 $tr = 't';
f72fc647 91 if ($form_id == 'devel_admin_settings' || $form_id == 'devel_node_access_admin_settings') {
969760e9 92 $form['devel_node_access'] = array(
93 '#type' => 'fieldset',
94 '#title' => t('Devel Node Access'),
95 '#collapsible' => TRUE,
96 );
97 $form['devel_node_access']['devel_node_access_debug_mode'] = array(
f72fc647 98 '#type' => 'checkbox',
969760e9 99 '#title' => t('Debug mode'),
f72fc647 100 '#default_value' => variable_get('devel_node_access_debug_mode', FALSE),
171d50c8 101 '#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.<br />For even more information enable the <a href="@link">%DNAbU block</a>, too.', array(
102 '!Rebuild_permissions' => l('[' . $tr('Rebuild permissions') . ']', 'admin/reports/status/rebuild'),
103 '%DNAbU' => t('Devel Node Access by User'),
104 '@link' => url('admin/structure/block/list'),
105 )),
f72fc647 106 );
969760e9 107 // push the Save button down
108 $form['actions']['#weight'] = 10;
f72fc647 109 }
110}
111
361dbb76 112function dna_summary() {
969760e9 113 // warn user if they have any entries that could grant access to all nodes
91d809c6 114 $output = '';
1094cb03 115 $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid = 0');
aa3e9ef2 116 $rows = array();
1094cb03 117 foreach ($result as $row) {
aa3e9ef2 118 $rows[] = array($row->realm);
119 }
91d809c6 120 if (!empty($rows)) {
1094cb03 121 $output .= '<h3>' . t('Access Granted to All Nodes (All Users)') . "</h3>\n";
122 $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 123 $headers = array(t('realm'));
1094cb03 124 $output .= theme('table', array('header' => $headers, 'rows' => $rows));
a2d2ff9f 125 $access_granted_to_all_nodes = TRUE;
361dbb76
DC
126 }
127
128 // how many nodes are not represented in the node_access table
1094cb03 129 $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();
130 if ($num) {
131 $output .= '<h3>' . t('Legacy Nodes') . "</h3>\n";
132 $output .= '<p>' .
edaf11c1 133 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 134 . "</p>\n";
a2d2ff9f 135 if (!empty($access_granted_to_all_nodes)) {
1094cb03 136 $output .= '<p>' .
a2d2ff9f 137 t('This issue may be masked by the one above, so look into the former first.')
1094cb03 138 . "</p>\n";
a2d2ff9f 139 }
361dbb76
DC
140 }
141 else {
1094cb03 142 $output .= '<h3>' . t('All Nodes Represented') . "</h3>\n";
143 $output .= '<p>' . t('All nodes are represented in the node_access table.') . "</p>\n";
361dbb76
DC
144 }
145
146
147 // a similar warning to the one above, but slightly more specific
edaf11c1 148 $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid <> 0');
aa3e9ef2 149 $rows = array();
1094cb03 150 foreach ($result as $row) {
aa3e9ef2 151 $rows[] = array($row->realm);
152 }
91d809c6 153 if (!empty($rows)) {
1094cb03 154 $output .= '<h3>' . t('Access Granted to All Nodes (Some Users)') . "</h3>\n";
155 $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 156 $headers = array(t('realm'));
1094cb03 157 $output .= theme('table', array('header' => $headers, 'rows' => $rows));
361dbb76
DC
158 }
159
160
161 // find specific nodes which may be visible to all users
edaf11c1 162 $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 163 $rows = array();
1094cb03 164 foreach ($result as $row) {
969760e9 165 $rows[] = array(
166 $row->realm,
167 array(
168 'data' => $row->node_count,
169 'align' => 'center',
170 ),
171 );
aa3e9ef2 172 }
91d809c6 173 if (!empty($rows)) {
1094cb03 174 $output .= '<h3>' . t('Access Granted to Some Nodes') . "</h3>\n";
175 $output .= '<p>' .
969760e9 176 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 177 . "</p>\n";
91d809c6 178 $headers = array(t('realm'), t('public nodes'));
1094cb03 179 $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Public Nodes')));
361dbb76 180 }
3c1264d3 181
361dbb76
DC
182
183 // find specific nodes protected by node_access table
edaf11c1 184 $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 185 $rows = array();
1094cb03 186 foreach ($result as $row) {
f72fc647 187 // no Views yet:
188 //$rows[] = array(l($row->realm, "devel/node_access/view/$row->realm"),
969760e9 189 $rows[] = array(
190 $row->realm,
191 array(
192 'data' => $row->node_count,
193 'align' => 'center',
194 ),
195 );
aa3e9ef2 196 }
91d809c6 197 if (!empty($rows)) {
1094cb03 198 $output .= '<h3>' . t('Summary by Realm') . "</h3>\n";
199 $output .= '<p>' . t('The following realms grant limited access to some specific nodes.') . "</p>\n";
91d809c6 200 $headers = array(t('realm'), t('private nodes'));
1094cb03 201 $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Protected Nodes')));
361dbb76 202 }
3c1264d3 203
361dbb76
DC
204 return $output;
205}
206
09344f2f 207function dna_visible_nodes($nid = NULL) {
361dbb76
DC
208 static $nids = array();
209 if ($nid) {
edaf11c1 210 $nids[$nid] = $nid;
361dbb76 211 }
f5e57e66 212 elseif (empty($nids) && arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == NULL) {
969760e9 213 // show DNA information on node/NID even if access is denied (IF the user has the 'view devel_node_access information' permission)!
214 return array(arg(1));
215 }
361dbb76
DC
216 return $nids;
217}
218
4749300a 219function devel_node_access_node_view($node, $build_mode) {
220 // remember this node, for display in our block
221 dna_visible_nodes($node->nid);
361dbb76
DC
222}
223
1094cb03 224function _devel_node_access_module_invoke_all() { // array and scalar returns
a6028226 225 $args = func_get_args();
1094cb03 226 $hook = $args[0];
227 unset($args[0]);
a6028226 228 $return = array();
229 foreach (module_implements($hook) as $module) {
1094cb03 230 $function = $module . '_' . $hook;
231 if (function_exists($function)) {
232 $result = call_user_func_array($function, $args);
233 if (isset($result)) {
234 if (is_array($result)) {
235 foreach ($result as $key => $value) {
236 // add name of module that returned the value:
237 $result[$key]['#module'] = $module;
238 }
239 }
240 else {
241 // build array with result keyed by $module:
242 $result = array($module => $result);
243 }
244 $return = array_merge_recursive($return, $result);
a6028226 245 }
a6028226 246 }
247 }
248 return $return;
249}
250
969760e9 251/**
252 * Helper function to build an associative array of grant records and their
253 * history. If there are duplicate records, display an error message.
254 *
255 * @param $grants
256 * An indexed array of grant records, augmented by the '#module' key,
257 * as created by _devel_node_access_module_invoke_all('node_access_records').
258 *
259 * @param $node
260 * The node that the grant records belong to.
261 *
262 * @param $function
263 * The name of the hook that produced the grants array, in case we need to
264 * display an error message.
265 *
266 * @return
267 * See _devel_node_access_nar_alter() for the description of the result.
268 */
269function _devel_node_access_build_nar_data($grants, $node, $function) {
270 $data = array();
271 $duplicates = array();
272 foreach ($grants as $grant) {
273 if (empty($data[$grant['realm']][$grant['gid']])) {
274 $data[$grant['realm']][$grant['gid']] = array('original' => $grant, 'current' => $grant, 'changes' => array());
275 }
276 else {
277 if (empty($duplicates[$grant['realm']][$grant['gid']])) {
278 $duplicates[$grant['realm']][$grant['gid']][] = $data[$grant['realm']][$grant['gid']]['original'];
279 }
280 $duplicates[$grant['realm']][$grant['gid']][] = $grant;
281 }
282 }
283 if (!empty($duplicates)) {
284 // generate an error message
285 $msg = t('Devel Node Access has detected duplicate records returned from %function:', array('%function' => $function));
286 $msg .= '<ul>';
287 foreach ($duplicates as $realm => $data_by_realm) {
288 foreach ($data_by_realm as $gid => $data_by_realm_gid) {
289 $msg .= '<li><ul>';
290 foreach ($data_by_realm_gid as $grant) {
291 $msg .= "<li>$node->nid/$realm/$gid/" . ($grant['grant_view'] ? 1 : 0) . ($grant['grant_update'] ? 1 : 0) . ($grant['grant_delete'] ? 1 : 0) . ' by ' . $grant['#module'] . '</li>';
292 }
293 $msg .= '</ul></li>';
294 }
295 }
296 $msg .= '</ul>';
297 drupal_set_message($msg, 'error', FALSE);
298 }
299 return $data;
300}
301
302/**
303 * Helper function to mimic hook_node_access_records_alter() and trace what
304 * each module does with it.
305 *
306 * @param object $grants
307 * An indexed array of grant records, augmented by the '#module' key,
308 * as created by _devel_node_access_module_invoke_all('node_access_records').
309 * This array is updated by the hook_node_access_records_alter()
310 * implementations.
311 *
312 * @param $node
313 * The node that the grant records belong to.
314 *
315 * @return
316 * A tree representation of the grant records in $grants including their
317 * history:
318 * $data[$realm][$gid]
319 * ['original'] - grant record before processing
320 * ['current'] - grant record after processing (if still present)
321 * ['changes'][]['op'] - change message (add/change/delete by $module)
322 * ['grant'] - grant record after change (unless deleted)
323 */
324function _devel_node_access_nar_alter(&$grants, $node) {
325 //dpm($grants, '_devel_node_access_nar_alter(): grants IN');
326 $dummy = array();
327 drupal_alter('node_access_records', $dummy, $node);
328 static $drupal_static = array();
329 isset($drupal_static['drupal_alter']) || ($drupal_static['drupal_alter'] = &drupal_static('drupal_alter'));
330 $functions = $drupal_static['drupal_alter'];
331
332 // build the initial tree (and check for duplicates)
333 $data = _devel_node_access_build_nar_data($grants, $node, 'hook_node_access_records()');
334
335 // simulate drupal_alter('node_access_records', $grants, $node);
336 foreach ($functions['node_access_records'] as $function) {
337 // call hook_node_access_records_alter() for one module at a time and analyze
338 $function($grants, $node); // <==
339 $module = substr($function, 0, strlen($function) - 26);
340
341 foreach ($grants as $i => $grant) {
342 if (empty($data[$grant['realm']][$grant['gid']]['current'])) {
343 // it's an added grant
344 $data[$grant['realm']][$grant['gid']]['current'] = $grant;
345 $data[$grant['realm']][$grant['gid']]['current']['#module'] = $module;
346 $data[$grant['realm']][$grant['gid']]['changes'][] = array(
347 'op' => 'added by ' . $module,
348 'grant' => $grant,
349 );
350 $grants[$i]['#module'] = $module;
351 }
352 else {
353 // it's an existing grant, check for changes
354 foreach (array('view', 'update', 'delete') as $op) {
355 $$op = $grant["grant_$op"] - $data[$grant['realm']][$grant['gid']]['current']["grant_$op"];
356 }
357 $priority = $grant['priority'] - $data[$grant['realm']][$grant['gid']]['current']['priority'];
358 if ($view || $update || $delete || $priority) {
359 // it was changed
360 $data[$grant['realm']][$grant['gid']]['current'] = $grant;
361 $data[$grant['realm']][$grant['gid']]['current']['#module'] = $module;
362 $data[$grant['realm']][$grant['gid']]['changes'][] = array(
363 'op' => 'altered by ' . $module,
364 'grant' => $grant,
365 );
366 $grants[$i]['#module'] = $module;
367 }
368 }
369 $data[$grant['realm']][$grant['gid']]['found'] = TRUE;
370 }
371
372 // check for newly introduced duplicates
373 _devel_node_access_build_nar_data($grants, $node, 'hook_node_access_records_alter()');
374
375 // look for grant records that have disappeared
376 foreach ($data as $realm => $data2) {
377 foreach ($data2 as $gid => $data3) {
378 if (empty($data[$realm][$gid]['found']) && isset($data[$realm][$gid]['current'])) {
379 unset($data[$realm][$gid]['current']);
380 $data[$realm][$gid]['changes'][] = array('op' => 'removed by ' . $module);
381 }
382 else {
383 unset($data[$realm][$gid]['found']);
384 }
385 }
386 }
387 }
388 //dpm($data, '_devel_node_access_nar_alter() returns');
389 //dpm($grants, '_devel_node_access_nar_alter(): grants OUT');
390 return $data;
391}
392
393/**
394 * Helper function to mimic hook_node_grants_alter() and trace what
395 * each module does with it.
396 *
397 * @param object $grants
398 * An indexed array of grant records, augmented by the '#module' key,
399 * as created by _devel_node_access_module_invoke_all('node_grants').
400 * This array is updated by the hook_node_grants_alter()
401 * implementations.
402 *
403 * @param $node
404 * The node that the grant records belong to.
405 *
406 * @return
407 * A tree representation of the grant records in $grants including their
408 * history:
409 * $data[$realm][$gid]
410 * ['cur'] - TRUE or FALSE whether the gid is present or not
411 * ['ori'][] - array of module names that contributed this grant (if any)
412 * ['chg'][] - array of changes, such as
413 * - 'added' if module name is a prefix if the $realm,
414 * - 'added by module' otherwise, or
415 * - 'removed by module'
416 */
417function _devel_node_access_ng_alter(&$grants, $account, $op) {
418 //dpm($grants, '_devel_node_access_ng_alter(): grants IN');
419 $dummy = array();
420 drupal_alter('node_grants', $dummy, $account, $op);
421 static $drupal_static = array();
422 isset($drupal_static['drupal_alter']) || ($drupal_static['drupal_alter'] = &drupal_static('drupal_alter'));
423 $functions = $drupal_static['drupal_alter'];
424
425 // build the initial structure
426 $data = array();
427 foreach ($grants as $realm => $gids) {
428 foreach ($gids as $i => $gid) {
429 if ($i !== '#module') {
430 $data[$realm][$gid]['cur'] = TRUE;
431 $data[$realm][$gid]['ori'][] = $gids['#module'];
432 }
433 }
434 unset($grants[$realm]['#module']);
435 }
436
437 // simulate drupal_alter('node_grants', $grants, $account, $op);
438 foreach ($functions['node_grants'] as $function) {
439 // call hook_node_grants_alter() for one module at a time and analyze
440 $function($grants, $account, $op); // <==
441 $module = substr($function, 0, strlen($function) - 18);
442
443 // check for new gids
444 foreach ($grants as $realm => $gids) {
445 foreach ($gids as $i => $gid) {
446 if (empty($data[$realm][$gid]['cur'])) {
447 $data[$realm][$gid]['cur'] = TRUE;
448 $data[$realm][$gid]['chg'][] = 'added by ' . $module;
449 }
450 }
451 }
452
453 // check for removed gids
454 foreach ($data as $realm => $gids) {
455 foreach ($gids as $gid => $history) {
456 if ($history['cur'] && array_search($gid, $grants[$realm]) === FALSE) {
f5e57e66 457 $data[$realm][$gid]['cur'] = FALSE;
969760e9 458 $data[$realm][$gid]['chg'][] = 'removed by ' . $module;
459 }
460 }
461 }
462 }
463
464 //dpm($data, '_devel_node_access_ng_alter() returns');
465 //dpm($grants, '_devel_node_access_ng_alter(): grants OUT');
466 return $data;
467}
468
f5e57e66 469/**
470 * Implements hook_block_info().
471 */
616d7cb9 472function devel_node_access_block_info() {
473 $blocks['dna_node'] = array(
969760e9 474 'info' => t('Devel Node Access'),
616d7cb9 475 'region' => 'footer',
476 'status' => 1,
969760e9 477 'cache' => DRUPAL_NO_CACHE,
616d7cb9 478 );
479 $blocks['dna_user'] = array(
969760e9 480 'info' => t('Devel Node Access by User'),
616d7cb9 481 'region' => 'footer',
969760e9 482 'cache' => DRUPAL_NO_CACHE,
616d7cb9 483 );
4749300a 484 return $blocks;
485}
486
f5e57e66 487/**
488 * Implements hook_block_view().
489 */
616d7cb9 490function devel_node_access_block_view($delta) {
a6028226 491 global $user;
1094cb03 492 global $theme_key;
493 static $block1_visible, $hint = '';
494 if (!isset($block1_visible)) {
969760e9 495 $block1_visible = db_query("SELECT status FROM {block} WHERE module = 'devel_node_access' AND delta = 'dna_user' AND theme = :theme", array(
496 ':theme' => $theme_key,
497 ))->fetchField();
1094cb03 498 if (!$block1_visible) {
171d50c8 499 $hint = t('For per-user access permissions enable the <a href="@link">%DNAbU block</a>.', array('@link' => url('admin/structure/block'), '%DNAbU' => t('Devel Node Access by User')));
1094cb03 500 }
501 }
502
4749300a 503 if (!user_access(DNA_ACCESS_VIEW)) {
504 return;
505 }
a8520a04 506
507 $output = array();
508
4749300a 509 switch ($delta) {
616d7cb9 510 case 'dna_node':
4749300a 511 if (!count(dna_visible_nodes())) {
edaf11c1 512 return;
513 }
b5d6be8c 514
4749300a 515 // include rows where nid == 0
516 $nids = array_merge(array(0 => 0), dna_visible_nodes());
1094cb03 517 $query = db_select('node_access', 'na');
1094cb03 518 $query
519 ->fields('na')
1094cb03 520 ->condition('na.nid', $nids, 'IN')
521 ->orderBy('na.nid')
522 ->orderBy('na.realm')
523 ->orderBy('na.gid');
2f19ebc0 524 $nodes = node_load_multiple($nids);
4749300a 525
526 if (!variable_get('devel_node_access_debug_mode', FALSE)) {
527 $headers = array(t('node'), t('realm'), t('gid'), t('view'), t('update'), t('delete'), t('explained'));
528 $rows = array();
1094cb03 529 foreach ($query->execute() as $row) {
4749300a 530 $explained = module_invoke_all('node_access_explain', $row);
969760e9 531 $rows[] = array(
532 (empty($row->nid) ? '0' : '<a href="#node-' . $row->nid . '">' . _devel_node_access_get_node_title($nodes[$row->nid], TRUE) . '</a>'),
533 $row->realm,
534 $row->gid,
535 $row->grant_view,
536 $row->grant_update,
537 $row->grant_delete,
538 implode('<br />', $explained),
539 );
4749300a 540 }
a8520a04 541 $output[] = array(
542 '#theme' => 'table',
543 '#header' => $headers,
544 '#rows' => $rows,
545 '#attributes' => array('style' => 'text-align: left')
546 );
547
969760e9 548 $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')))) . (empty($hint) ? '' : ' ' . $hint);
4749300a 549 }
550 else {
551 $tr = 't';
1094cb03 552 $variables = array('!na' => '{node_access}');
4749300a 553 $states = array(
969760e9 554 'default' => array(t('default'), 'ok', t('Default grant supplied by core in the absence of any other non-empty grants; in !na.', $variables)),
555 'ok' => array(t('ok'), 'ok', t('Highest priority grant; in !na.', $variables)),
556 'removed' => array(t('removed'), '', t('Was removed in @func; not in !na.', $variables + array('@func' => 'hook_node_access_records_alter()'))),
1094cb03 557 'static' => array(t('static'), 'ok', t('Non-standard grant in !na.', $variables)),
969760e9 558 'unexpected' => array(t('unexpected'), 'warning', t('The 0/all/0/... grant applies to all nodes and all users -- usually it should not be present in !na if any node access module is active!')),
559 'ignored' => array(t('ignored'), 'warning', t('Lower priority grant; not in !na and thus ignored.', $variables)),
1094cb03 560 'empty' => array(t('empty'), 'warning', t('Does not grant any access, but could block lower priority grants; not in !na.', $variables)),
969760e9 561 'wrong' => array(t('wrong'), 'error', t('Is rightfully in !na but at least one access flag is wrong!', $variables)),
1094cb03 562 'missing' => array(t('missing'), 'error', t("Should be in !na but isn't!", $variables)),
969760e9 563 'removed!' => array(t('removed!'), 'error', t('Was removed in @func; should NOT be in !na!', $variables + array('@func' => 'hook_node_access_records_alter()'))),
1094cb03 564 'illegitimate' => array(t('illegitimate'), 'error', t('Should NOT be in !na because of lower priority!', $variables)),
565 'alien' => array(t('alien'), 'error', t('Should NOT be in !na because of unknown origin!', $variables)),
4749300a 566 );
969760e9 567 $active_states = array('default', 'ok', 'static', 'unexpected', 'wrong', 'illegitimate', 'alien');
568 $headers = array(t('node'), t('prio'), t('status'), t('realm'), t('gid'), t('view'), t('update'), t('delete'), t('explained'));
569 $headers = _devel_node_access_format_row($headers);
4749300a 570 $active_grants = array();
1094cb03 571 foreach ($query->execute() as $active_grant) {
4749300a 572 $active_grants[$active_grant->nid][$active_grant->realm][$active_grant->gid] = $active_grant;
573 }
969760e9 574 $all_grants = $checked_grants = $published_nid = array();
4749300a 575 foreach ($nids as $nid) {
576 $acquired_grants_nid = array();
90bc9415 577 if ($node = node_load($nid)) {
4749300a 578 // check node_access_acquire_grants()
579 $grants = _devel_node_access_module_invoke_all('node_access_records', $node);
969760e9 580 // check drupal_alter('node_access_records')
581 $data = _devel_node_access_nar_alter($grants, $node);
582 /* (This was the D6 implementation that didn't analyze the hook_node_access_records_alter() details.)
4749300a 583 if (!empty($grants)) {
584 $top_priority = NULL;
585 foreach ($grants as $grant) {
586 $priority = intval($grant['priority']);
587 $top_priority = (isset($top_priority) ? max($top_priority, $priority) : $priority);
588 $grant['priority'] = (isset($grant['priority']) ? $priority : '&ndash;&nbsp;');
589 $acquired_grants_nid[$priority][$grant['realm']][$grant['gid']] = $grant + array(
969760e9 590 '#title' => _devel_node_access_get_node_title($node, TRUE),
4749300a 591 '#module' => (isset($grant['#module']) ? $grant['#module'] : ''),
592 );
593 }
594 krsort($acquired_grants_nid);
a6028226 595 }
969760e9 596 /*/
597 // (This is the new D7 implementation; it retains backward compatibility.)
598 if (!empty($data)) {
599 foreach ($data as $data_by_realm) {
600 foreach ($data_by_realm as $data_by_realm_gid) { // by gid
601 if (isset($data_by_realm_gid['current'])) {
602 $grant = $data_by_realm_gid['current'];
603 }
604 elseif (isset($data_by_realm_gid['original'])) {
605 $grant = $data_by_realm_gid['original'];
606 $grant['#removed'] = 1;
607 }
608 else {
609 continue;
610 }
611 $priority = intval($grant['priority']);
612 $top_priority = (isset($top_priority) ? max($top_priority, $priority) : $priority);
613 $grant['priority'] = (isset($grant['priority']) ? $priority : '&ndash;&nbsp;');
614 $grant['history'] = $data_by_realm_gid;
615 $acquired_grants_nid[$priority][$grant['realm']][$grant['gid']] = $grant + array(
616 '#title' => _devel_node_access_get_node_title($node),
617 '#module' => (isset($grant['#module']) ? $grant['#module'] : ''),
618 );
619 }
620 }
621 krsort($acquired_grants_nid);
622 }
623 /**/
624 //dpm($acquired_grants_nid, "acquired_grants_nid =");
4749300a 625 // check node_access_grants()
969760e9 626 $published_nid[$nid] = $node->status;
ec2981bc 627 if ($node->nid) {
4749300a 628 foreach (array('view', 'update', 'delete') as $op) {
021f83e1 629 $grants = _devel_node_access_module_invoke_all('node_grants', $user, $op);
969760e9 630 // call all hook_node_grants_alter() implementations
631 $ng_alter_data = _devel_node_access_ng_alter($grants, $user, $op);
021f83e1 632 $checked_grants[$nid][$op] = array_merge(array('all' => array(0)), $grants);
4749300a 633 }
a6028226 634 }
4749300a 635 }
969760e9 636
4749300a 637 // check for grants in the node_access table that aren't returned by node_access_acquire_grants()
2ac23102 638
4749300a 639 if (isset($active_grants[$nid])) {
640 foreach ($active_grants[$nid] as $realm => $active_grants_realm) {
641 foreach ($active_grants_realm as $gid => $active_grant) {
2ac23102 642 $found = FALSE;
4749300a 643 $count_nonempty_grants = 0;
644 foreach ($acquired_grants_nid as $priority => $acquired_grants_nid_priority) {
645 if (isset($acquired_grants_nid_priority[$realm][$gid])) {
646 $found = TRUE;
a6028226 647 }
648 }
4749300a 649 if ($acquired_grants_nid_priority = reset($acquired_grants_nid)) { // highest priority only
650 foreach ($acquired_grants_nid_priority as $acquired_grants_nid_priority_realm) {
651 foreach ($acquired_grants_nid_priority_realm as $acquired_grants_nid_priority_realm_gid) {
652 $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 653 }
a6028226 654 }
655 }
1094cb03 656 $fixed_grant = (array) $active_grant;
969760e9 657 if ($count_nonempty_grants == 0 && $realm == 'all' && $gid == 0) {
1094cb03 658 $fixed_grant += array(
4749300a 659 'priority' => '&ndash;',
660 'state' => 'default',
661 );
662 }
663 elseif (!$found) {
1094cb03 664 $acknowledged = _devel_node_access_module_invoke_all('node_access_acknowledge', $fixed_grant);
665 if (empty($acknowledged)) {
969760e9 666 // no module acknowledged this record, mark it as alien
1094cb03 667 $fixed_grant += array(
668 'priority' => '?',
669 'state' => 'alien',
670 );
671 }
672 else {
969760e9 673 // at least one module acknowledged the record, attribute it to the first one
1094cb03 674 $fixed_grant += array(
675 'priority' => '&ndash;',
676 'state' => 'static',
969760e9 677 '#module' => reset(array_keys($acknowledged)),
1094cb03 678 );
679 }
4749300a 680 }
681 else {
682 continue;
683 }
684 $fixed_grant += array(
969760e9 685 'nid' => $nid,
686 '#title' => _devel_node_access_get_node_title($node),
4749300a 687 );
688 $all_grants[] = $fixed_grant;
a6028226 689 }
4749300a 690 }
691 }
969760e9 692
4749300a 693 // order grants and evaluate their status
694 foreach ($acquired_grants_nid as $priority => $acquired_grants_priority) {
695 ksort($acquired_grants_priority);
696 foreach ($acquired_grants_priority as $realm => $acquired_grants_realm) {
697 ksort($acquired_grants_realm);
698 foreach ($acquired_grants_realm as $gid => $acquired_grant) {
699 if ($priority == $top_priority) {
700 if (empty($acquired_grant['grant_view']) && empty($acquired_grant['grant_update']) && empty($acquired_grant['grant_delete'])) {
701 $acquired_grant['state'] = 'empty';
702 }
703 else {
969760e9 704 if (isset($active_grants[$nid][$realm][$gid])) {
705 $acquired_grant['state'] = (isset($acquired_grant['#removed']) ? 'removed!' : 'ok');
706 }
707 else {
708 $acquired_grant['state'] = (isset($acquired_grant['#removed']) ? 'removed' : 'missing');
709 }
4749300a 710 if ($acquired_grant['state'] == 'ok') {
711 foreach (array('view', 'update', 'delete') as $op) {
712 $active_grant = (array) $active_grants[$nid][$realm][$gid];
969760e9 713 if (empty($acquired_grant["grant_$op"]) != empty($active_grant["grant_$op"])) {
4749300a 714 $acquired_grant["grant_$op!"] = $active_grant["grant_$op"];
a6028226 715 }
716 }
717 }
a6028226 718 }
719 }
4749300a 720 else {
721 $acquired_grant['state'] = (isset($active_grants[$nid][$realm][$gid]) ? 'illegitimate' : 'ignored');
a6028226 722 }
4749300a 723 $all_grants[] = $acquired_grant + array('nid' => $nid);
a6028226 724 }
a6028226 725 }
4749300a 726 }
727 }
969760e9 728
4749300a 729 // fill in the table rows
730 $rows = array();
731 $error_count = 0;
732 foreach ($all_grants as $grant) {
733 $row = new stdClass();
734 $row->nid = $grant['nid'];
735 $row->title = $grant['#title'];
736 $row->priority = $grant['priority'];
737 $row->state = array('data' => $states[$grant['state']][0], 'title' => $states[$grant['state']][2]);
738 $row->realm = $grant['realm'];
739 $row->gid = $grant['gid'];
740 $row->grant_view = $grant['grant_view'];
741 $row->grant_update = $grant['grant_update'];
742 $row->grant_delete = $grant['grant_delete'];
1094cb03 743 $row->explained = implode('<br />', module_invoke_all('node_access_explain', $row));
45773576 744 unset($row->title); // possibly needed above
4749300a 745 if ($row->nid == 0 && $row->gid == 0 && $row->realm == 'all' && count($all_grants) > 1) {
746 $row->state = array('data' => $states['unexpected'][0], 'title' => $states['unexpected'][2]);
747 $class = $states['unexpected'][1];
748 }
749 else {
750 $class = $states[$grant['state']][1];
751 }
4749300a 752 $row = (array) $row;
753 foreach (array('view', 'update', 'delete') as $op) {
754 $row["grant_$op"] = array('data' => $row["grant_$op"]);
18a9129e 755 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']) && in_array($grant['state'], $active_states)) {
4749300a 756 $row["grant_$op"]['data'] .= '&prime;';
757 $row["grant_$op"]['title'] = t('This entry grants access to this node to this user.');
deab5cc3 758 }
4749300a 759 if (isset($grant["grant_$op!"])) {
0a3a11be 760 $row["grant_$op"]['data'] = $grant["grant_$op!"] . '&gt;' . (!$row["grant_$op"]['data'] ? 0 : $row["grant_$op"]['data']);
1094cb03 761 $row["grant_$op"]['class'][] = 'error';
969760e9 762 if ($class == 'ok') {
763 $row['state'] = array('data' => $states['wrong'][0], 'title' => $states['wrong'][2]);
764 $class = $states['wrong'][1];
765 }
a6028226 766 }
4749300a 767 }
969760e9 768 $error_count += ($class == 'error');
769 $row['nid'] = array(
770 'data' => '<a href="#node-' . $grant['nid'] . '">' . $row['nid'] . '</a>',
771 'title' => $grant['#title'],
772 );
1094cb03 773 $row['realm'] = (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0 ? '' : $grant['#module'] . ':<br />') . $grant['realm'];
969760e9 774
775 // prepend information from the D7 hook_node_access_records_alter()
776 $next_style = array();
777 if (isset($grant['history'])) {
778 $history = $grant['history'];
779 if (($num_changes = count($history['changes']) - empty($history['current'])) > 0) {
780 $first_row = TRUE;
781 while (isset($history['original']) || $num_changes--) {
782 if (isset($history['original'])) {
783 $this_grant = $history['original'];
784 $this_action = '[ Original by ' . $this_grant['#module'] . ':';
785 unset($history['original']);
786 }
787 else {
788 $change = $history['changes'][0];
789 $this_grant = $change['grant'];
790 $this_action = ($first_row ? '[ ' : '') . $change['op'] . ':';
791 array_shift($history['changes']);
792 }
793 $rows[] = array(
794 'data' => array(
795 'data' => array(
796 'data' => $this_action,
797 'style' => array('padding-bottom: 0;'),
798 ),
799 ),
800 'style' => array_merge(($first_row ? array() : array('border-top-style: dashed;', 'border-top-width: 1px;')), array('border-bottom-style: none;')),
801 );
802 $next_style = array('border-top-style: none;');
803 if (count($history['changes'])) {
804 $g = $this_grant;
805 $rows[] = array(
806 'data' => array('v', $g['priority'], '', $g['realm'], $g['gid'], $g['grant_view'], $g['grant_update'], $g['grant_delete'], 'v'),
807 'style' => array('border-top-style: none;', 'border-bottom-style: dashed;'),
808 );
809 $next_style = array('border-top-style: dashed;');
810 }
811 $first_row = FALSE;
812 }
813 }
814 }
815
314a481e 816 // fix up the main row cells with the proper class (needed for Bartik)
817 foreach ($row as $key => $value) {
818 if (!is_array($value)) {
819 $row[$key] = array('data' => $value);
820 }
821 $row[$key]['class'] = array($class);
822 }
969760e9 823 // add the main row
824 $will_append = empty($history['current']) && !empty($history['changes']);
825 $rows[] = array(
826 'data' => array_values($row),
827 'class' => array($class),
828 'style' => array_merge($next_style, ($will_append ? array('border-bottom-style: none;') : array())),
829 );
830
831 // append information from the D7 hook_node_access_records_alter()
832 if ($will_append) {
833 $last_change = end($history['changes']);
834 $rows[] = array(
835 'data' => array(
836 'data' => array(
837 'data' => $last_change['op'] . ' ]',
838 'style' => array('padding-top: 0;'),
839 ),
840 ),
841 'style' => array('border-top-style: none;'),
842 );
843 }
844 }
845
846 foreach ($rows as $i => $row) {
847 $rows[$i] = _devel_node_access_format_row($row);
4749300a 848 }
a8520a04 849
850 $output[] = array(
851 '#theme' => 'table',
852 '#header' => $headers,
853 '#rows' => $rows,
854 '#attributes' => array(
855 'class' => array('system-status-report'),
856 'style' => 'text-align: left;',
969760e9 857 ),
a8520a04 858 );
4749300a 859
a8520a04 860 $output[] = array(
861 '#theme' => 'form_element',
969760e9 862 '#description' => t('(Some of the table elements provide additional information if you hover your mouse over them.)'),
a8520a04 863 );
4749300a 864
865 if ($error_count > 0) {
1094cb03 866 $variables['!Rebuild_permissions'] = '<a href="' . url('admin/reports/status/rebuild') . '">' . $tr('Rebuild permissions') . '</a>';
a8520a04 867 $output[] = array(
868 '#prefix' => "\n<div class=\"error\">",
869 '#markup' => 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),
870 '#suffix' => "</div><br />\n",
871 );
4749300a 872 }
873
1094cb03 874 // Explain whether access is granted or denied, and why (using code from node_access()).
17507063 875 $tr = 't';
1094cb03 876 array_shift($nids); // remove the 0
877 $accounts = array();
878 $variables += array(
969760e9 879 '!username' => '<em class="placeholder">' . theme('username', array('account' => $user)) . '</em>',
880 '%uid' => $user->uid,
1094cb03 881 );
882
883 if (user_access('bypass node access')) {
884 $variables['%bypass_node_access'] = $tr('bypass node access');
a8520a04 885 $output[] = array(
886 '#markup' => t('!username has the %bypass_node_access permission and thus full access to all nodes.', $variables),
887 '#suffix' => '<br />&nbsp;',
888 );
4749300a 889 }
890 else {
969760e9 891 $variables['!list'] = '<div style="margin-left: 2em">' . _devel_node_access_get_grant_list($nid, $ng_alter_data) . '</div>';
2b28ebc9 892 $variables['%access'] = 'view';
a8520a04 893 $output[] = array(
894 '#prefix' => "\n<div style='text-align: left' title='" . t('These are the grants returned by hook_node_grants() for this user.') . "'>",
895 '#markup' => t('!username (user %uid) can use these grants (if they are present above) for %access access: !list', $variables),
896 '#suffix' => "</div>\n",
897 );
1094cb03 898 $accounts[] = $user;
899 }
900 if (arg(0) == 'node' && is_numeric(arg(1)) && !$block1_visible) { // only for single nodes
901 if (user_is_logged_in()) {
902 $accounts[] = user_load(0); // Anonymous, too
4749300a 903 }
1094cb03 904 foreach ($accounts as $account) {
a8520a04 905 $account_items = array();
1094cb03 906 $nid_items = array();
907 foreach ($nids as $nid) {
908 $op_items = array();
909 foreach (array('create', 'view', 'update', 'delete') as $op) {
910 $explain = _devel_node_access_explain_access($op, $nid, $account);
911 $op_items[] = "<div style='width: 5em; display: inline-block'>" . t('%op:', array('%op' => $op)) . ' </div>' . $explain[2];
a6028226 912 }
a8520a04 913 $nid_items[] = array(
914 '#theme' => 'item_list',
915 '#items' => $op_items,
916 '#type' => 'ul',
917 '#prefix' => t('to node !nid:', array('!nid' => l($nid, 'node/' . $nid))) . "\n<div style='margin-left: 2em'>",
918 '#suffix' => '</div>',
919 );
a6028226 920 }
1094cb03 921 if (count($nid_items) == 1) {
a8520a04 922 $account_items = $nid_items[0];
1094cb03 923 }
924 else {
a8520a04 925 $account_items = array(
926 '#theme' => 'item_list',
927 '#items' => $nid_items,
928 '#type' => 'ul',
929 '#prefix' => "\n<div style='margin-left: 2em'>",
930 '#suffix' => '</div>',
931 );
1094cb03 932 }
a8520a04 933 $variables['!username'] = theme('username', array('account' => $account));
934 $output[] = array(
935 '#prefix' => "\n<div style='text-align: left'>",
936 '#markup' => t("!username has the following access", $variables),
937 'items' => $account_items,
938 '#suffix' => "\n</div>\n",
939 );
a6028226 940 }
4749300a 941 }
942 }
a6028226 943
1094cb03 944 if (!empty($hint)) {
a8520a04 945 $output[] = array(
946 '#theme' => 'form_element',
947 '#description' => '(' . $hint . ')',
948 );
1094cb03 949 }
a8520a04 950 $output[]['#markup'] = '<br /><br />';
4749300a 951 $subject = t('node_access entries for nodes shown on this page');
a8520a04 952 return array('subject' => $subject, 'content' => $output);
4749300a 953
616d7cb9 954 case 'dna_user':
4749300a 955 // show which users can access this node
1f40e1c9 956 if (arg(0) == 'node' && is_numeric($nid = arg(1)) && arg(2) == NULL && $node = node_load($nid)) {
1094cb03 957 $node_type = node_type_get_type($node);
958 $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 959 $rows = array();
960 // Find all users. The following operations are very inefficient, so we
961 // limit the number of users returned. It would be better to make a
962 // pager query, or at least make the number of users configurable. If
963 // anyone is up for that please submit a patch.
63e43f1c 964 $query = db_select('users', 'u')
965 ->fields('u', array('uid'))
1094cb03 966 ->orderBy('access', 'DESC')
63e43f1c 967 ->range(0, 9);
968 $uids = $query->execute()->fetchCol();
969 array_unshift($uids, 0);
970 $accounts = user_load_multiple($uids);
971 foreach ($accounts as $account) {
972 $username = theme('username', array('account' => $account));
26a5c344 973 if ($account->uid == $user->uid) {
974 $username = '<strong>' . $username . '</strong>';
975 }
969760e9 976 $rows[] = array(
26a5c344 977 $username,
969760e9 978 theme('dna_permission', _devel_node_access_explain_access('create', $nid, $account)),
979 theme('dna_permission', _devel_node_access_explain_access('view', $nid, $account)),
980 theme('dna_permission', _devel_node_access_explain_access('update', $nid, $account)),
981 theme('dna_permission', _devel_node_access_explain_access('delete', $nid, $account)),
4749300a 982 );
983 }
984 if (count($rows)) {
a8520a04 985 $output[] = array(
986 '#theme' => 'table',
987 '#header' => $headers,
988 '#rows' => $rows,
989 '#attributes' => array('style' => 'text-align: left'),
990 );
991 $output[] = array(
992 '#theme' => 'form_element',
993 '#description' => t('(This table lists the most-recently active users. Hover your mouse over each result for more details.)'),
994 );
995
969760e9 996 return array(
997 'subject' => t('Access permissions by user'),
998 'content' => $output,
999 );
4749300a 1000 }
edaf11c1 1001 }
b5d6be8c 1002 break;
361dbb76
DC
1003 }
1004}
1005
aa60cf2c 1006/**
1094cb03 1007 * Helper function that mimicks node.module's node_access() function.
14bc219f 1008 *
1094cb03 1009 * Unfortunately, this needs to be updated manually whenever node.module changes!
14bc219f 1010 *
1094cb03 1011 * @return
1012 * An array suitable for theming with theme_dna_permission().
1013 */
1014function _devel_node_access_explain_access($op, $node, $account = NULL) {
1015 global $user;
1016
1017 if (is_numeric($node) && !($node = node_load($node))) {
969760e9 1018 return array(
1019 FALSE,
1020 '???',
1094cb03 1021 t('Unable to load the node &ndash; this should never happen!'),
1022 );
1023 }
1024 if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
969760e9 1025 return array(
1026 FALSE,
1027 t('!NO: invalid $op', array('!NO' => t('NO'))),
1094cb03 1028 t("'@op' is an invalid operation!", array('@op' => $op)),
1029 );
1030 }
1031
1032 if ($op == 'create' && is_object($node)) {
1033 $node = $node->type;
1034 }
1035
1036 if (!empty($account)) {
1037 // To try to get the most authentic result we impersonate the given user!
1038 // This may reveal bugs in other modules, leading to contradictory results.
1039 $saved_user = $user;
1040 drupal_save_session(FALSE);
1041 $user = $account;
1042 $result = _devel_node_access_explain_access($op, $node, NULL);
1043 $user = $saved_user;
1044 drupal_save_session(TRUE);
1045 $second_opinion = node_access($op, $node, $account);
1046 if ($second_opinion != $result[0]) {
de40f600 1047 $result[1] .= '<span class="' . ($second_opinion ? 'ok' : 'error') . '" title="Core seems to disagree on this item. This is a bug in either DNA or Core and should be fixed! Try to look at this node as this user and check whether there is still disagreement.">*</span>';
1094cb03 1048 }
1049 return $result;
1050 }
1051
1052 $variables = array(
969760e9 1053 '!NO' => t('NO'),
1054 '!YES' => t('YES'),
1055 '!bypass_node_access' => t('bypass node access'),
1056 '!access_content' => t('access content'),
1094cb03 1057 );
1058
1059 if (user_access('bypass node access')) {
969760e9 1060 return array(
1061 TRUE,
1094cb03 1062 t('!YES: bypass node access', $variables),
969760e9 1063 t("!YES: This user has the '!bypass_node_access' permission and may do everything with nodes.", $variables),
1094cb03 1064 );
1065 }
1066
1067 if (!user_access('access content')) {
969760e9 1068 return array(
1069 FALSE,
1094cb03 1070 t('!NO: access content', $variables),
969760e9 1071 t("!NO: This user does not have the '!access_content' permission and is denied doing anything with content.", $variables),
1094cb03 1072 );
1073 }
1074
1075 foreach (module_implements('node_access') as $module) {
1076 $function = $module . '_node_access';
1077 if (function_exists($function)) {
1078 $result = $function($node, $op, $user);
1079 if ($module == 'node') {
1080 $module = 'node (permissions)';
1081 }
1082 if (isset($result)) {
1083 if ($result === NODE_ACCESS_DENY) {
1084 $denied_by[] = $module;
1085 }
1086 elseif ($result === NODE_ACCESS_ALLOW) {
1087 $allowed_by[] = $module;
1088 }
1089 $access[] = $result;
1090 }
1091 }
1092 }
1093 $variables += array(
969760e9 1094 '@deniers' => (empty($denied_by) ? NULL : implode(', ', $denied_by)),
1094cb03 1095 '@allowers' => (empty($allowed_by) ? NULL : implode(', ', $allowed_by)),
1096 );
1097 if (!empty($denied_by)) {
1098 $variables += array(
54229123 1099 '%module' => $denied_by[0] . (count($denied_by) > 1 ? '+' : ''),
1094cb03 1100 );
969760e9 1101 return array(
1102 FALSE,
1094cb03 1103 t('!NO: by %module', $variables),
969760e9 1104 empty($allowed_by)
1094cb03 1105 ? t("!NO: hook_node_access() of the following module(s) denies this: @deniers.", $variables)
969760e9 1106 : 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),
1094cb03 1107 );
1108 }
1109 if (!empty($allowed_by)) {
1110 $variables += array(
54229123 1111 '%module' => $allowed_by[0] . (count($allowed_by) > 1 ? '+' : ''),
969760e9 1112 '!view_own_unpublished_content' => t('view own unpublished content'),
1094cb03 1113 );
969760e9 1114 return array(
1115 TRUE,
1094cb03 1116 t('!YES: by %module', $variables),
1117 t("!YES: hook_node_access() of the following module(s) allows this: @allowers.", $variables),
1118 );
1119 }
1120
1121 if ($op == 'view' && !$node->status && user_access('view own unpublished content') && $user->uid == $node->uid && $user->uid != 0) {
969760e9 1122 return array(
1123 TRUE,
1094cb03 1124 t('!YES: view own unpublished content', $variables),
969760e9 1125 t("!YES: The node is unpublished, but the user has the '!view_own_unpublished_content' permission.", $variables),
1094cb03 1126 );
1127 }
1128
1129 if ($op != 'create' && $node->nid) {
1130 if (node_access($op, $node)) { // delegate this part
c9be5ce7 1131 $variables['@node_access_table'] = '{node_access}';
969760e9 1132 return array(
1133 TRUE,
c9be5ce7 1134 t('!YES: @node_access_table', $variables),
1135 t('!YES: Node access allows this based on one or more records in the @node_access_table table (see the other DNA block!).', $variables),
1094cb03 1136 );
1137 }
1138 }
1139
969760e9 1140 return array(
1141 FALSE,
1094cb03 1142 t('!NO: no reason', $variables),
969760e9 1143 t("!NO: None of the checks resulted in allowing this, so it's denied.", $variables)
1144 . ($op == 'create' ? ' ' . t('This is most likely due to a withheld permission.') : ''),
1094cb03 1145 );
1146}
1147
1148/**
1149 * Helper function to create a list of the grants returned by hook_node_grants().
1150 */
969760e9 1151function _devel_node_access_get_grant_list($nid, $ng_alter_data) {
1152 //dpm($ng_alter_data, "_devel_node_access_get_grant_list($nid,");
f5e57e66 1153 $ng_alter_data = array_merge(array('all' => array(0 => array('cur' => TRUE, 'ori' => array('all')))), $ng_alter_data);
969760e9 1154 $items = array();
1155 if (count($ng_alter_data)) {
1156 foreach ($ng_alter_data as $realm => $gids) {
1157 ksort($gids);
1158 $gs = array();
1159 foreach ($gids as $gid => $history) {
1160 if ($history['cur']) {
1161 if (isset($history['ori'])) {
1162 $g = $gid; // original grant, still active
1163 }
1164 else {
1165 $g = '<u>' . $gid . '</u>'; // new grant, still active
1166 }
1167 }
1168 else {
1169 $g = '<del>' . $gid . '</del>'; // deleted grant
1094cb03 1170 }
969760e9 1171
1172 $ghs = array();
1173 if (isset($history['ori']) && strpos($realm, $history['ori'][0]) !== 0) {
1174 $ghs[] = 'by ' . $history['ori'][0];
1175 }
1176 if (isset($history['chg'])) {
1177 foreach ($history['chg'] as $h) {
1178 $ghs[] = $h;
1179 }
1180 }
1181 if (!empty($ghs)) {
1182 $g .= ' (' . implode(', ', $ghs) . ')';
1183 }
1184 $gs[] = $g;
1094cb03 1185 }
f5e57e66 1186 $items[] = $realm . ': ' . implode(', ', $gs);
1094cb03 1187 }
969760e9 1188 if (!empty($items)) {
1189 return theme('item_list', array('items' => $items, 'type' => 'ul'));
1094cb03 1190 }
1191 }
1192}
1193
1194/**
62ee392b 1195 * Implements hook_node_access_explain().
edaf11c1 1196 */
aa60cf2c 1197function devel_node_access_node_access_explain($row) {
fec1eb09 1198 if ($row->gid == 0 && $row->realm == 'all') {
a2d2ff9f 1199 foreach (array('view', 'update', 'delete') as $op) {
1094cb03 1200 $gop = 'grant_' . $op;
a2d2ff9f 1201 if (!empty($row->$gop)) {
1202 $ops[] = $op;
1203 }
1204 }
1205 if (empty($ops)) {
14bc219f 1206 return '(No access granted to ' . ($row->nid == 0 ? 'any nodes.)' : 'this node.)');
d73b19bd 1207 }
edaf11c1 1208 else {
1094cb03 1209 return 'All users may ' . implode('/', $ops) . ($row->nid == 0 ? ' all nodes.' : ' this node.');
fec1eb09 1210 }
aa60cf2c 1211 }
1212}
361dbb76 1213
44404c01 1214/**
1094cb03 1215 * Helper function to return a sanitized node title.
1216 */
969760e9 1217function _devel_node_access_get_node_title($node, $clip_and_decorate = FALSE) {
1094cb03 1218 if (isset($node)) {
9aec84e1 1219 if (isset($node->title)) {
969760e9 1220 $node_title = check_plain(!is_array($node->title) ? $node->title : $node->title[LANGUAGE_NONE][0]['value']);
1094cb03 1221 if ($clip_and_decorate) {
1222 if (drupal_strlen($node_title) > 20) {
1223 $node_title = "<span title='node/$node->nid: $node_title'>" . drupal_substr($node_title, 0, 15) . '...</span>';
1224 }
1225 $node_title = '<span title="node/' . $node->nid . '">' . $node_title . '</span>';
1226 }
1227 return $node_title;
1228 }
1229 elseif (isset($node->nid)) {
1230 return $node->nid;
1231 }
1232 }
1233 return '&mdash;';
1234}
1235
1236/**
969760e9 1237 * Helper function to apply common formatting to a debug-mode table row.
1238 */
1239function _devel_node_access_format_row($row, $may_unpack = TRUE) {
1240 if ($may_unpack && isset($row['data'])) {
1241 $row['data'] = _devel_node_access_format_row($row['data'], FALSE);
1242 $row['class'][] = 'even';
1243 return $row;
1244 }
1245 if (count($row) == 1) {
1246 if (is_scalar($row['data'])) {
1247 $row['data'] = array('data' => $row['data']);
1248 }
1249 $row['data']['colspan'] = 9;
1250 }
1251 else {
1252 $row = array_values($row);
1253 foreach (array(0, 1, 4) as $j) { // node, prio, gid
1254 if (is_scalar($row[$j])) {
1255 $row[$j] = array('data' => $row[$j]);
1256 }
1257 $row[$j]['style'][] = 'text-align: right;';
1258 }
1259 }
1260 return $row;
1261}
1262
1263/**
09344f2f 1264 * Implementation of hook_theme().
44404c01 1265 */
1266function devel_node_access_theme() {
1267 return array(
1268 'dna_permission' => array(
969760e9 1269 'arguments' => array('permission' => NULL),
44404c01 1270 ),
1271 );
1272}
1273
1274/**
1275 * Indicate whether user has a permission or not.
44404c01 1276 */
1277function theme_dna_permission($permission) {
1094cb03 1278 return '<span class="' . ($permission[0] ? 'ok' : 'error') . '" title="' . $permission[2] . '">' . $permission[1] . '</span>';
44404c01 1279}