Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.101
diff -u -r1.101 module.inc
--- includes/module.inc	7 May 2007 11:59:40 -0000	1.101
+++ includes/module.inc	16 May 2007 21:04:59 -0000
@@ -236,6 +236,7 @@
   foreach ($module_list as $module) {
     if (module_exists($module)) {
       module_load_install($module);
+      module_disabling($module);
       module_invoke($module, 'disable');
       db_query("UPDATE {system} SET status = 0, throttle = 0 WHERE type = 'module' AND name = '%s'", $module);
       $invoke_modules[] = $module;
@@ -250,6 +251,14 @@
   }
 }
 
+function module_disabling($module = NULL) {
+  static $disabling = array();
+  if ($module) {
+    $disabling[] = $module;
+  }
+  return $disabling;
+}
+
 /**
  * @defgroup hooks Hooks
  * @{
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.814
diff -u -r1.814 node.module
--- modules/node/node.module	16 May 2007 13:45:16 -0000	1.814
+++ modules/node/node.module	16 May 2007 21:05:00 -0000
@@ -1084,8 +1084,7 @@
  * Handler for wipe confirmation
  */
 function node_configure_rebuild_confirm_submit(&$form, $form, &$form_state) {
-  node_access_rebuild();
-  drupal_set_message(t('The node access table has been rebuilt.'));
+  batch_set(node_access_rebuild_batch());
   $form_state['redirect'] = 'admin/content/node-settings';
   return;
 }
@@ -2890,8 +2889,11 @@
  *
  * @param $node
  *   The $node to acquire grants for.
+ * @param $delete
+ *   If false, do not delete records. This is only for optimization purposes,
+ *   and assumes the caller has already performed a mass delete of some form.
  */
-function node_access_acquire_grants($node) {
+function node_access_acquire_grants($node, $delete = TRUE) {
   $grants = module_invoke_all('node_access_records', $node);
   if (!$grants) {
     $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
@@ -2906,7 +2908,7 @@
     $grants = array_shift($grant_by_priority);
   }
 
-  node_access_write_grants($node, $grants);
+  node_access_write_grants($node, $grants, NULL, $delete);
 }
 
 /**
@@ -2957,18 +2959,89 @@
 /**
  * Rebuild the node access database. This is occasionally needed by modules
  * that make system-wide changes to access levels.
+ *
+ * This function uses batch processing, allowing this possibly time-consuming
+ * operation to be spawned over several HTTP requests and avoid PHP timeout.
+ *
+ * hook_update, hook_enable/disable, and any form submit handler are
+ * safe places to use this. Other cases might consider using the 'regular'
+ * (non-batch) node_access_rebuild function instead.
+ */
+function node_access_rebuild_batch() {
+  db_query("DELETE FROM {node_access}");
+  // Only recalculate if the site is using a node_access module (one that is
+  // not just being disabled).
+  $node_access_modules = array_diff(module_implements('node_grants'), module_disabling());
+  if (count($node_access_modules)) {
+    $batch = array(
+      'title' => t('Rebuilding content access permissions'),
+      'operations' => array(
+        array('node_access_rebuild_batch_operation', array()),
+      ),
+      'finished' => 'node_access_rebuild_batch_finished'
+    );
+    batch_set($batch);
+  }
+  else {
+    // not using any node_access modules. add the default grant.
+    db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
+    cache_clear_all();
+  }
+}
+
+/**
+ * Batch operation for node_access_rebuild_batch :
+ * Rebuild the access grants for all nodes 5 by 5.
+ */
+function node_access_rebuild_batch_operation(&$context) {
+  if (empty($context['sandbox'])) {
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['current_node'] = 0;
+    $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
+  }
+  $limit = 5;
+  $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", $context['sandbox']['current_node'], 0, $limit);
+  while ($row = db_fetch_array($result)) {
+    $node = node_load($row['nid'], NULL, TRUE);
+    node_access_acquire_grants($node, FALSE);
+    $context['sandbox']['progress']++;
+    $context['sandbox']['current_node'] = $node->nid;
+    $context['message'] = $node->title;
+  }
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+}
+
+/**
+ * Post-processing for node_access_rebuild_batch
+ */
+function node_access_rebuild_batch_finished($success, $results, $operations) {
+  if ($success) {
+    drupal_set_message(t('The content access permissions have been rebuilt.'));
+  }
+  else {
+    drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
+  }
+  cache_clear_all();
+}
+
+/**
+ * Rebuild the node access database (non-batch version).
  */
 function node_access_rebuild() {
   db_query("DELETE FROM {node_access}");
-  // only recalculate if site is using a node_access module
-  if (count(module_implements('node_grants'))) {
+  // Only recalculate if the site is using a node_access module (one that is
+  // not just being disabled).
+  $node_access_modules = array_diff(module_implements('node_grants'), module_disabling());
+  if (count($node_access_modules)) {
     // If not in 'safe mode', increase the maximum execution time:
     if (!ini_get('safe_mode')) {
       set_time_limit(240);
     }
     $result = db_query("SELECT nid FROM {node}");
     while ($node = db_fetch_object($result)) {
-      node_access_acquire_grants(node_load($node->nid, NULL, TRUE));
+      node_access_acquire_grants(node_load($node->nid, NULL, TRUE), TRUE);
     }
   }
   else {
@@ -2977,6 +3050,7 @@
   }
   cache_clear_all();
 }
+
 /**
  * @} End of "defgroup node_access".
  */
