Index: devel_node_access.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/devel/devel_node_access.module,v
retrieving revision 1.15.2.14
diff -u -r1.15.2.14 devel_node_access.module
--- devel_node_access.module	25 Jul 2009 19:59:18 -0000	1.15.2.14
+++ devel_node_access.module	21 Aug 2009 22:08:48 -0000
@@ -418,54 +418,43 @@
               $output .= theme_item(array('#value' => '<div class="error">'. t("You have errors in your !na table! You may be able to fix these for now by running !Rebuild_permissions, but this is likely to destroy the evidence and make it impossible to identify the underlying issues. If you don't fix those, the errors will probably come back again. <br /> DON'T do this just yet if you intend to ask for help with this situation.", $tokens) .'</div>'));
             }
 
-            // explain how access is granted (code from node_access())
-            $t = 't';
-            if (user_access('administer nodes')) {
-              $output .= t('This user has the %administer_nodes permission and thus full access to all nodes.', array('%administer_nodes' => $t('administer nodes')));
-            }
-            else {
-
-              function devel_node_access_message($nid, $by_what) {
-                $t = 't';
-                return '<div class="form-item" style="text-align: left">'. theme_markup(array('#value' => t('This user is granted %view access to node %nid !by_what', array('%view' => $t('view'), '%nid' => $nid, '!by_what' => $by_what)))) .'</div>';
+            // explain how access is granted (using code from node_access())
+            $variables = array(
+              '!username' => theme('username', $user),
+              '!list' => '<div style="margin-left: 2em">'. _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants) .'</div>',
+            );
+            $output .= "\n<div style='text-align: left' title='". t('These are the grants returned by hook_node_grants() for this user.') ."'>". t('!username can use the following grants (if they are present above): !list', $variables) ."</div>\n";
+
+            if (arg(0) == 'node' && is_numeric(arg(1))) {  // only for single nodes
+              $t = 't';
+              array_shift($nids);  // remove the 0
+              $accounts[] = $user;
+              if (user_is_logged_in()) {
+                $accounts[] = user_load(0);  // Anonymous, too
               }
-
-              foreach ($nids as $nid) {
-                if ($node = node_load(array('nid' => $nid))) {
-                  $module = node_get_types('module', $node);
-                  if ($module == 'node') {
-                    $module = 'node_content'; // Avoid function name collisions.
-                  }
-                  $access = module_invoke($module, 'access', 'view', $node, $user);
-                  if (!empty($access)) {
-                    $output .= devel_node_access_message($nid, t('by the %module module itself.', array('%module' => $module)));
-                  }
-                  else {
-                    if (!empty($checked_status[$nid])) {
-                      $cgs_by_realm = array();
-                      foreach ($checked_grants[$nid]['view'] as $realm => $cg) {
-                        if (isset($cg['#module'])) {
-                          $module = $cg['#module'];
-                          unset($cg['#module']);
-                          if (!empty($module) && (strpos($realm, $module) !== 0)) {
-                            $realm = $module .':'. $realm;
-                          }
-                        }
-                        $cgs_by_realm[$realm] = $realm .': '. implode(', ', $cg);
-                      }
-                      if (!empty($cgs_by_realm)) {
-                        $output .= devel_node_access_message($nid, t("by one or more of the following grants (if they are present above): !list", array('!list' => theme('item_list', array_values($cgs_by_realm), NULL, 'ul'))));
-                      }
-                      elseif ($user->uid == $node->uid && $user->uid != 0) {
-                        $output .= devel_node_access_message($nid, t('as author of the node.'));
-                      }
-                    }
+              foreach ($accounts as $account) {
+                $variables['!username'] = theme('username', $account);
+                $output .= "\n<div style='text-align: left'>". t("!username has the following access", $variables) .' ';
+                $nid_items = array();
+                foreach ($nids as $nid) {
+                  $op_items = array();
+                  foreach (array('view', 'update', 'delete') as $op) {
+                    $op_items[] = "<div style='width: 5em; display: inline-block'>". t('%op: ', array('%op' => $op)) .'</div>'.
+                      _devel_node_access_explain_access($op, $nid, $account);
                   }
+                  $nid_items[] = t('to node !nid:', array('!nid' => l($nid, 'node/'. $nid))) 
+                    ."\n<div style='margin-left: 2em'>". theme('item_list', $op_items, NULL, 'ul') .'</div>';
+                }
+                if (count($nid_items) == 1) {
+                  $output .= $nid_items[0];
+                }
+                else {
+                  $output .= "\n<div style='margin-left: 2em'>". theme('item_list', $nid_items, NULL, 'ul') .'</div>';
                 }
+                $output .= "\n</div>\n";
               }
             }
           }
-
           $subject = t('node_access entries for nodes shown on this page');
           return array('subject' => $subject, 'content' => $output .'<br /><br />');
 
@@ -480,17 +469,18 @@
             // limit the number of users returned.  It would be better to make a
             // pager query, or at least make the number of users configurable.  If
             // anyone is up for that please submit a patch.
-            $result = db_query_range('SELECT DISTINCT u.* FROM {users} u ORDER BY u.access DESC', 0, 10);
+            $result = db_query_range('SELECT DISTINCT u.* FROM {users} u ORDER BY u.access ASC', 0, 20);
             while ($data = db_fetch_object($result)) {
               $account = user_load(array('uid' => $data->uid));
               $rows[] = array(theme('username', $data),
-                              theme('dna_permission', node_access('view', $node, $account)),
-                              theme('dna_permission', node_access('update', $node, $account)),
-                              theme('dna_permission', node_access('delete', $node, $account)),
+                              theme('dna_permission', node_access('view', $node, $account), _devel_node_access_explain_access('view', $nid, $account)),
+                              theme('dna_permission', node_access('update', $node, $account), _devel_node_access_explain_access('update', $nid, $account)),
+                              theme('dna_permission', node_access('delete', $node, $account), _devel_node_access_explain_access('delete', $nid, $account)),
               );
             }
             if (count($rows)) {
               $output = theme('table', $headers, $rows, array('style' => 'text-align: left'));
+              $output .= theme_item(array('#value' => '', '#description' => t('(This table lists the most-recently active users. Hover your mouse over each result so see the reason for allowing or denying access.)')));
               return array('subject' => t('Access permissions by user'),
                            'content' => $output);
             }
@@ -502,6 +492,138 @@
 }
 
 /**
+ * Helper function that mimicks node.module's node_access() function.
+ * Unfortunately, this needs to be updated manually whenever node.module changes!
+ */
+function _devel_node_access_explain_access($op, $nid, $account = NULL)
+{
+  global $user;
+
+  if (!empty($account)) {
+    // To try to get the most authentic result we impersonate the given user!
+    // This may reveal bugs in other modules, leading to contradictory results.
+    $saved_user = $user;
+    session_save_session(FALSE);
+    $user = $account;
+    $result = _devel_node_access_explain_access($op, $nid);
+    $user = $saved_user;
+    session_save_session(TRUE);
+    return $result;
+  }
+
+  if (!($node = node_load(array('nid' => $nid)))) {
+    return '???';  // this should not happen!
+  }
+  if ($op == 'update' && !_devel_node_access_filter_access($node->format)) {
+    return t('!NO: input format (@format) is not accessible.', array('!NO' => t('NO'), '@format' => $node->format));
+  }
+  if (user_access('administer nodes')) {
+    return t("!YES: user has '@permission.'", array('!YES' => t('YES'), '@permission' => 'administer nodes'));
+  }
+  if (!user_access('access content')) {
+    return t("!NO: user does not have '@permission.'", array('!NO' => t('NO'), '@permission' => 'access content'));
+  }
+  $module = node_get_types('module', $node);
+  if ($module == 'node') {
+    $module = 'node_content';
+  }
+  $access = module_invoke($module, 'access', 'view', $node, $user);
+  if (!is_null($access)) {
+    return t("!YES: by the '@module' module itself.", array('!YES' =>($access ? t('YES') : t('NO')), '@module' => $module));
+  }
+
+  if ($op != 'create' && $node->nid && $node->status) {
+    $access = node_access($op, $node, $account);  // delegate this part
+    return t('!YES: by node access.', array('!YES' =>($access ? t('YES') : t('NO'))));;
+  }
+
+  if ($op == 'view' && $account->uid == $node->uid && $account->uid != 0) {
+    return t('!YES: user may view their own node.', array('!YES' => t('YES')));
+  }
+  return t('!NO: no reason to allow.', array('!NO' => t('NO')));
+}
+
+/*
+ * Helper function that mimicks filter.modules' filter_access(), but with the
+ * help of the user-aware _devel_node_access_filter_formats() function.
+ */
+function _devel_node_access_filter_access($format) {
+  $format = filter_resolve_format($format);
+  if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) {
+    return TRUE;
+  }
+  else {
+    return (bool) _devel_node_access_filter_formats($format);
+  }
+}
+
+/*
+ * Helper function that mimicks filter.modules' filter_formats(), but for a
+ * specific user. If #470840 gets committed, we can drop this here.
+ */
+function _devel_node_access_filter_formats($index, $account = NULL) {
+  global $user;
+  static $formats = array();
+
+  if (!isset($account)) {
+    $account = $user;
+  }
+
+  // Administrators can always use all input formats.
+  $all = user_access('administer filters', $account);
+
+  if (!isset($formats[$account->uid])) {
+    $formats[$account->uid] = array();
+
+    $query = 'SELECT * FROM {filter_formats}';
+
+    // Build query for selecting the format(s) based on the user's roles.
+    $args = array();
+    if (!$all) {
+      $where = array();
+      foreach ($account->roles as $rid => $role) {
+        $where[] = "roles LIKE '%%,%d,%%'";
+        $args[] = $rid;
+      }
+      $query .= ' WHERE '. implode(' OR ', $where) .' OR format = %d';
+      $args[] = variable_get('filter_default_format', 1);
+    }
+
+    $result = db_query($query, $args);
+    while ($format = db_fetch_object($result)) {
+      $formats[$account->uid][$format->format] = $format;
+    }
+  }
+  if (isset($index)) {
+    return isset($formats[$account->uid][$index]) ? $formats[$account->uid][$index] : FALSE;
+  }
+  return $formats[$account->uid];
+}
+
+/**
+ * Helper function to create a list of the grants returned by hook_node_grants().
+ */
+function _devel_node_access_get_grant_list($nid, $checked_status, $checked_grants)
+{
+  if (!empty($checked_status[$nid])) {
+    $cgs_by_realm = array();
+    foreach ($checked_grants[$nid]['view'] as $realm => $cg) {
+      if (isset($cg['#module'])) {
+        $module = $cg['#module'];
+        unset($cg['#module']);
+        if (!empty($module) && (strpos($realm, $module) !== 0)) {
+          $realm = $module .':'. $realm;
+        }
+      }
+      $cgs_by_realm[$realm] = $realm .': '. implode(', ', $cg);
+    }
+    if (!empty($cgs_by_realm)) {
+      return theme('item_list', array_values($cgs_by_realm), NULL, 'ul');
+    }
+  }
+}
+
+/**
  * Implementation of hook_node_access_explain().
  */
 function devel_node_access_node_access_explain($row) {
@@ -534,14 +656,7 @@
 
 /**
  * Indicate whether user has a permission or not.
- *
- * TODO: use good looking images.
  */
-function theme_dna_permission($permission) {
-  if ($permission) {
-    return t('yes');
-  }
-  else {
-    return t('no');
-  }
+function theme_dna_permission($permission, $title) {
+  return '<span title="'. $title .'">'. ($permission ? t('yes') : t('no')) .'</span>';
 }
