--- nodeperm_role.module.4_6_3	2005-12-06 16:22:49.000000000 -0800
+++ nodeperm_role.module	2005-12-06 16:28:38.000000000 -0800
@@ -110,6 +110,10 @@
   $help = t('<div id="help">Drupal\'s default behavior is to reset options to their <a href="%default-options-link">defaults</a> when a node is edited. Change this behavior by indicating, below, which options will be preserved when a user edits a node they don\'t own. Users with &quot;administer nodes&quot; permission may override these settings within the node form. </div>', array('%default-options-link' => url('admin/node/configure/types')));
   $output .= form_group(t('Editing options'), $help. $group);
 
+  // CRG : Add an advanced option that will allow for incremental changes to access instead of replacement changes
+  $html = form_checkbox(t("Allow incremental per-role settings"), 'nodeperm_role_inherit', 1, variable_get('nodeperm_role_inherit',0), 'Allows actions to inherit, remove or set permissions for roles. Inheriting means any existing permissions for this role are retained by this action.');
+  $output .= form_group(t('Advanced options'), $html, 'Changes to individual actions will not occur until the actions are edited and saved again');
+
   return $output;
 }
 
@@ -169,12 +173,17 @@
  * Save node permissions by writing them to the database.
  */
 function nodeperm_save($node) {
-  if ($node->nodeperm_role_view || $node->nodeperm_role_edit) {
+  // If either of the special array are set then update node_access. Original code used just || which
+  // failed when all permissions were being taken away.
+  if (is_array($node->nodeperm_role_view) || is_array($node->nodeperm_role_edit)) {
     $roles = nodeperm_get_roles();
     db_query("DELETE FROM {node_access} WHERE nid = %d AND realm = 'nodeperm_role'", $node->nid);
     $grants = array();
+
+    // Get current node permissions
     $node->nodeperm_role_view = isset($node->nodeperm_role_view) ? $node->nodeperm_role_view : array();
     $node->nodeperm_role_edit = isset($node->nodeperm_role_edit) ? $node->nodeperm_role_edit : array();
+
     /* A rid of -1 means that the user doesn't want anybody else to edit their entry. */
     foreach ($node->nodeperm_role_view as $rid) {
       if ($rid != -1) {
@@ -189,12 +198,18 @@
     }
 
     foreach ($grants as $rid => $grant) {
-      db_query('INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (%d, %d, \'nodeperm_role\', %d, %d, %d)', $node->nid, $rid, $grant['view'], $grant['edit'], $grant['edit']);
-      if ($grant['view']) {
-        $rnames_view[] = $roles[$rid];
-      }
-      if ($grant['edit']) {
-        $rnames_edit[] = $roles[$rid];
+      // Optimize the node access table. If all permissions are FALSE then do not bother adding it (deny is the default)
+      //  - In theory reducing node access rows will improve performance
+      //  - Note that edit permission is also used for delete permission
+      if ($grant['view'] || $grant['edit']) {
+        // %d format control will auto-convert undefined values to 0 which is what is desired
+        db_query('INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (%d, %d, \'nodeperm_role\', %d, %d, %d)', $node->nid, $rid, $grant['view'], $grant['edit'], $grant['edit']);
+        if ($grant['view']) {
+          $rnames_view[] = $roles[$rid];
+        }
+        if ($grant['edit']) {
+          $rnames_edit[] = $roles[$rid];
+        }
       }
     }
     if ($rnames_view) {
@@ -248,7 +263,7 @@
   return $nodeperm_role_edit;
 }
 
-function nodeperm_get_roles() {
+function nodeperm_get_roles($none = False) {
   static $roles;
 
   if (!$roles) {
@@ -265,8 +280,21 @@
  * Implementation of a Drupal action.
  * Alters node level role permissions for a node.
  *
+ * If advanced inheritence flag is set then this action supports set/remove/inherit logic. This is the only
+ * place such code exists. Inheritence does not make any sense when a node is being edited as there is nothing
+ * to inherit from!
  */
 function action_node_assign_role_perms($op, &$edit, &$node) {
+  // Set some common values, used by many operations
+
+  // *** From module settings
+  $advanced_form_name = 'nodeperm_role_inherit';
+  $is_advanced_form = variable_get($advanced_form_name, 0);
+  // True if view permission is being maintained
+  $is_view = variable_get('nodeperm_role_mode', 0) == 0;      
+
+
+  // Handle operation
   switch($op) {
     case 'metadata':
       return array(
@@ -278,10 +306,67 @@
       break;
 
     case 'do':
+      // If node contains nodeperm settings use them, otherwise fetch them
+      //  - In most cases the data will not exist, but allow for it in case others have done some modifications
+      //  - Only bother getting values if advanced flag is set
+      //  - Make sure defaults are initialized to arrays
+      //  - When done each element contains the role IDs currently granted the specific permission
+      $default['view'] = array();
+      $default['edit'] = array();
+
+      if ($is_advanced_form) {
+        // Avoid grabbing view permissions if this has been deactivated
+        if ($is_view) {
+          if (is_array($node->nodeperm_role_view)) {
+            $default['view'] = $node->nodeperm_role_view;
+          } 
+          else {
+            $default['view'] = nodeperm_role_load_view($node->nid);
+          }
+        }
+        // edit (aka update) (also used for delete) is always grabbed
+        if (is_array($node->nodeperm_role_edit)) {
+          $default['edit'] = $node->nodeperm_role_edit;
+        } 
+        else {
+          $default['edit'] = nodeperm_role_load_view($node->nid);
+        }
+      } // End of inheritence support check
+            
+      // Loop through parameters passed via edit[]
+      // Handle new style inheritance settings in a way that is compatible with the old
+      //  - A teeny bit slower then original code but of little consequence for actions as they fire rarely (as compared to reads) in all but VERY extreme cases
+      $grants = array('view' => array(), 'edit' => array());      // Initialize final result set
+
+      foreach (nodeperm_get_roles() as $rid => $role_name) {
+        // Loop through each permission
+        foreach (nodeperm_role_perms() as $p) {
+          $set_name = 'nodeperm_role_'. $p;                     // Name of element holding rids that have explicit permissions
+          $inherit_name = 'nodeperm_role_'. $p . '_inherit';    // Name of element holding rids that inherit
+
+          if (in_array($rid, $edit[$set_name])) {
+            // permissions was explicitly specified for this role
+            $grants[$p][] = $rid;
+          }
+          elseif(in_array($rid, $edit[$inherit_name])) {
+            // permissions is inherited. If this rid is in default set then add it to result set
+            if (in_array($rid, $default[$p])) {
+              $grants[$p][] = $rid;
+            }
+          }
+          // else denial is assumed (does not appear in an array)
+        }
+      }
+
+      // Add back into node and save nodeperm data
       if (variable_get('nodeperm_role_mode', 0) == 0) {
-        $node->nodeperm_role_view = $edit['nodeperm_role_view'];
+        $node->nodeperm_role_view = $grants['view'];
       }
-      $node->nodeperm_role_edit = $edit['nodeperm_role_edit'];
+      $node->nodeperm_role_edit = $grants['edit'];
+
+      // Data is now in normal nodeperm format:
+      //  nodeperm_role_view = array of rids granted view
+      //  nodeperm_role_edit = array of rids granted edit
       nodeperm_save($node);
       break;
 
@@ -291,10 +376,77 @@
       $roles[-1] = t('none');
       ksort($roles);
       $output = '';
-      if (variable_get('nodeperm_role_mode', 0) == 0) {
-        $output .= form_select(t('Roles that may view the node'), 'nodeperm_role_view', $edit['nodeperm_role_view'], $roles, t('Select the roles that will be able to view the node.'), NULL, TRUE, FALSE);
+
+      // Get available permisisons, often used as a suffix for array keys
+      $perms = nodeperm_role_perms();
+
+
+      // Make sure elements are initialized
+      foreach (array('', '_inherit') as $type) {
+        foreach ($perms as $p) {
+          $name = 'nodeperm_role_' . $p . $type;
+          if (! $edit[$name]) {
+            $edit[$name] = array();
+          }
+        }
+      }
+
+      // Remember the type of form being generated
+      //  - Use a different name so detection between re-display and initial display is possible
+      $output .= form_hidden($advanced_form_name, $is_advanced_form);
+
+      if ($is_advanced_form) {
+        // CRG: Support the advanced permission handling
+
+        // If this is the first display of this form
+        //  - There is no preview so this check is probably not needed but do it anyway for completeness
+        if (! array_key_exists('nodeperm_role_form_type', $edit)) {
+          // Convert an array or roles to applicable options
+          // actions saves paramater data in $edit[] (same as CGI which normally makes it easy to transition between the two but doesn't help much now)
+          //  - This is NOT blazing fast but for the very few times it will be triggered (defining an action) it is fine
+          //  - This handles old style or new style
+          foreach ( nodeperm_get_roles() as $rid => $role_name) {
+            // Loop through each permission
+            foreach ($perms as $p) {
+              $cgi_name = 'nodeperm_role_'. $p .'_' . $rid;
+              if (in_array($rid, $edit['nodeperm_role_' . $p])) {
+                $edit[$cgi_name] = '1';
+              }
+              elseif($edit['nodeperm_role_' . $p . '_inherit'] && in_array($rid, $edit['nodeperm_role_' . $p . '_inherit'])) {
+                $edit[$cgi_name] = '-';
+              } else {
+                // Assume denial
+                $edit[$cgi_name] = '0';
+              }
+            }
+          }
+        }
+
+        $rows = array();
+        $columns = array();
+        $html .= '';
+        foreach ( nodeperm_get_roles() as $rid => $role_name) {
+          $columns = array($role_name);               // Role name
+          
+          foreach ($perms as $p) {
+            $cgi_name = 'nodeperm_role_'. $p .'_' . $rid;
+            $control = "<select name='edit[$cgi_name]'>";
+            $control .= sprintf("<option value='-' %s>-</option>\n", ($edit[$cgi_name] == '-') ? 'selected' : '');
+            $control .= sprintf("<option value='1' %s>Y</option>\n", ($edit[$cgi_name] == '1') ? 'selected' : '');
+            $control .= sprintf("<option value='0' %s>N</option>\n", ($edit[$cgi_name] == '0') ? 'selected' : '');
+            $control .= "</select>\n";
+            $columns[] = $control;
+          }
+          $rows[] = $columns;
+        }
+        $output .= form_group(t('Advanced role permissions'), theme('table', array_merge(array('role'), $perms), $rows), t("For each role select '-' to retain exsiting permission (no change) or 'Y' to grant permission or 'N' to deny permission"));
+      } else {
+        // Original code
+        if (variable_get('nodeperm_role_mode', 0) == 0) {
+          $output .= form_select(t('Roles that may view the node'), 'nodeperm_role_view', $edit['nodeperm_role_view'], $roles, t('Select the roles that will be able to view the node.'), NULL, TRUE, FALSE);
+        }
+        $output .= form_select(t('Roles that may edit the node'), 'nodeperm_role_edit', $edit['nodeperm_role_edit'], $roles, t('Select the roles that will be able to edit the node.'), NULL, TRUE, FALSE);
       }
-      $output .= form_select(t('Roles that may edit the node'), 'nodeperm_role_edit', $edit['nodeperm_role_edit'], $roles, t('Select the roles that will be able to edit the node.'), NULL, TRUE, FALSE);
 
       return $output;
       break;
@@ -302,16 +454,20 @@
     case 'validate':
       $errors = array();
 
-      if (!$edit['nodeperm_role_edit'] && $edit['nodeperm_role_view']) {
-        $errors['nodeperm_role_edit'] = t('Please choose the role(s) to transition to.');
-      }
+      // Validation only applies to simple interface
+      if (! $edit[$advanced_form_name]) {
+        if (!$edit['nodeperm_role_edit'] && $edit['nodeperm_role_view']) {
+          $errors['nodeperm_role_edit'] = t('Please choose the role(s) to transition to.');
+        }
 
-      foreach ((array) $edit['nodeperm_role_view'] as $key => $rid) {
-        $edit['nodeperm_role_view'][$key] = $rid;
-      }
-      foreach ((array) $edit['nodeperm_role_edit'] as $key => $rid) {
-        $edit['nodeperm_role_view'][$key] = $rid;
-        $edit['nodeperm_role_edit'][$key] = $rid;
+        foreach ((array) $edit['nodeperm_role_view'] as $key => $rid) {
+          $edit['nodeperm_role_view'][$key] = $rid;
+        }
+        foreach ((array) $edit['nodeperm_role_edit'] as $key => $rid) {
+          // CRG: view and edit can be different, allow this since other node access modules will be working with this one
+          //  $edit['nodeperm_role_view'][$key] = $rid;
+          $edit['nodeperm_role_edit'][$key] = $rid;
+        }
       }
 
       foreach ($errors as $name => $message) {
@@ -323,9 +479,72 @@
 
     // process the HTML form to store configuration
     case 'submit':
-      $params = array('nodeperm_role_edit' => $edit['nodeperm_role_edit'], 'nodeperm_role_view' => $edit['nodeperm_role_view']);
+      if ($edit[$advanced_form_name]) {
+        // If the new interface then translate the advanced settings into the old style
+        //  nodeperm_role_view_inherit - List of roles that inherit the view permission
+        //  nodeperm_role_edit_inherit - List of roles that inherit the edit permission
+        $params = array(
+          'nodeperm_role_edit' => array(),
+          'nodeperm_role_view' => array(),
+          'nodeperm_role_edit_inherit' => array(),
+          'nodeperm_role_view_inherit' => array());
+
+        foreach ( nodeperm_get_roles() as $rid => $role_name) {
+          // Loop through each permission - simular code as in 'form' but different enough to make factor hardly worth the effort
+          $perms = nodeperm_role_perms();
+          foreach ($perms as $p) {
+            $cgi_name = 'nodeperm_role_'. $p .'_' . $rid;     // Name of CGI form data
+            $param_name = 'nodeperm_role_' . $p;              // Paramater name to save rid in
+            if ($edit[$cgi_name]) {
+              switch ($edit[$cgi_name]) {
+                case '1':
+                  $params[$param_name][] = $rid;
+                  break;
+
+                case '-':
+                  $params[$param_name . '_inherit'][] = $rid;
+                  break;
+
+                // N is assumed if not in either array
+              }
+            }
+
+          } // end of loop through permissions
+        } // End of loop through roles
+      }
+      else {
+        // Old (simple) non-inheritance form data
+        // Inherit arrays are cleared just in case this is a transition from advanced to simple
+        $params = array(
+          'nodeperm_role_edit' => $edit['nodeperm_role_edit'],
+          'nodeperm_role_view' => $edit['nodeperm_role_view'],
+          'nodeperm_role_edit_inherit' => array(),
+          'nodeperm_role_view_inherit' => array());
+      }
+
       return $params;
       break;
     }
 }
+
+/*
+ * Returns an array of permissions that are being used.
+ *
+ * Note that internall the code reers to the update grant as 'edit'.
+ * This was retained during addition of inheritence code.
+ *
+ */
+
+function nodeperm_role_perms() {
+  if (variable_get('nodeperm_role_mode', 0) == 0) {
+    // Some think permissions slow things down but I have my doubts (CRG) since permissions are all on a single row in node_access
+    //  XXX If TRUE then always return view/edit
+    return array('view', 'edit');
+  }
+  else {
+    return array('edit');
+  }
+}
+
+
 ?>
