Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.813
diff -u -r1.813 node.module
--- modules/node/node.module	15 May 2007 05:43:16 -0000	1.813
+++ modules/node/node.module	15 May 2007 21:28:50 -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;
 }
@@ -2888,8 +2887,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);
@@ -2904,7 +2906,7 @@
     $grants = array_shift($grant_by_priority);
   }
 
-  node_access_write_grants($node, $grants);
+  node_access_write_grants($node, $grants, NULL, $delete);
 }
 
 /**
@@ -2955,6 +2957,95 @@
 /**
  * Rebuild the node access database. This is occasionally needed by modules
  * that make system-wide changes to access levels.
+ *
+ * This function defines a ready-to-use batch set, 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'
+ * node_access_rebuild instead.
+ *
+ * Usage :
+ * @code
+ * batch_set(node_access_rebuild_batch())
+ * @endcode
+ */
+function node_access_rebuild_batch() {
+  db_query("DELETE FROM {node_access}");
+  // only recalculate if site is using a node_access module
+  // TODO : would be cleaner if we could detect here that the last
+  // node access module is being disabled => no need to batch either.
+  // (see comment in node_access_rebuild_batch_operation)
+  // would require a global variable in module_disable ?
+  if (count(module_implements('node_grants'))) {
+    $batch = array(
+      'title' => t('Rebuilding content access permissions'),
+      'operations' => array(
+        array('node_access_rebuild_batch_operation', array()),
+      ),
+      'finished' => 'node_access_rebuild_batch_finished'
+    );
+    return $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) {
+  // When reacting to modules being disabled : by the time the batch
+  // does get processed, the modules have already been marked disabled,
+  // and node_access_write_grants won't write anything if there is no
+  // node access modules left active. No need to actually perform
+  // anything then, we will update the {node_access} table in
+  // node_access_rebuild_batch_finished.
+  if (count(module_implements('node_grants'))) {
+    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');
+  }
+
+  if (!count(module_implements('node_grants'))) {
+    db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
+  }
+  cache_clear_all();
+}
+
+/**
+ * Rebuild the node access database (non-batch version).
  */
 function node_access_rebuild() {
   db_query("DELETE FROM {node_access}");
@@ -2966,7 +3057,7 @@
     }
     $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 {
@@ -2975,6 +3066,7 @@
   }
   cache_clear_all();
 }
+
 /**
  * @} End of "defgroup node_access".
  */
