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