Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.105
diff -u -r1.105 module.inc
--- includes/module.inc	18 Jul 2007 14:10:14 -0000	1.105
+++ includes/module.inc	19 Jul 2007 21:47:08 -0000
@@ -260,6 +260,10 @@
 
   foreach ($invoke_modules as $module) {
     module_invoke($module, 'enable');
+    // Check if node_access table needs rebuilding.
+    if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
+      node_access_needs_rebuild(TRUE);
+    }
   }
 }
 
@@ -273,6 +277,11 @@
   $invoke_modules = array();
   foreach ($module_list as $module) {
     if (module_exists($module)) {
+      // Check if node_access table needs rebuilding.
+      if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
+        node_access_needs_rebuild(TRUE);
+      }
+
       module_load_install($module);
       module_invoke($module, 'disable');
       db_query("UPDATE {system} SET status = 0, throttle = 0 WHERE type = 'module' AND name = '%s'", $module);
@@ -286,6 +295,11 @@
     // Force to regenerate the stored list of hook implementations.
     module_implements('', FALSE, TRUE);
   }
+
+  // If there remains no more node_access module, we can rebuild right now.
+  if (node_access_needs_rebuild() && count(module_implements('node_grants')) == 0) {
+    node_access_rebuild();
+  }
 }
 
 /**
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.858
diff -u -r1.858 node.module
--- modules/node/node.module	16 Jul 2007 12:43:05 -0000	1.858
+++ modules/node/node.module	19 Jul 2007 21:47:10 -0000
@@ -19,6 +19,17 @@
  * Implementation of hook_help().
  */
 function node_help($path, $arg) {
+  if ($arg[0] == 'admin' && $path != 'admin/content/node-settings/rebuild' && strpos($path, '#') === FALSE
+      && user_access('administer nodes') && node_access_needs_rebuild()) {
+    if ($path == 'admin/content/node-settings') {
+      $message = t('The content access permissions need to be rebuilt.');
+    }
+    else {
+      $message = t('The content access permissions need to be rebuilt. Please visit <a href="@node_access_rebuild">this page</a>.', array('@node_access_rebuild' => url('admin/content/node-settings/rebuild')));
+    }
+    drupal_set_message($message, 'error');
+  }
+
   switch ($path) {
     case 'admin/help#node':
       $output = '<p>'. t('All content in a website is stored and treated as <b>nodes</b>. Therefore nodes are any postings such as blogs, stories, polls and forums. The node module manages these content types and is one of the strengths of Drupal over other content management systems.') .'</p>';
@@ -1076,7 +1087,8 @@
  * Menu callback; presents general node configuration options.
  */
 function node_configure() {
-  // Only show rebuild button if there is 0 or more than 2 rows in node_access table, or if there are modules that implement node_grant.
+  // Only show rebuild button if there is 0 or more than 2 rows in node_access table,
+  // or if there are modules that implement node_grant.
   if (db_result(db_query('SELECT COUNT(*) FROM {node_access}')) != 1 || count(module_implements('node_grants')) > 0) {
     $status = '<p>'. t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Possible causes for permission problems are disabling modules or configuration changes to permissions. Rebuilding will remove all privileges to posts, and replace them with permissions based on the current modules and settings.') .'</p>';
     $status .= '<p>'. t('Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed posts will automatically use the new permissions.') .'</p>';
@@ -1129,9 +1141,8 @@
 /**
  * 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.'));
+function node_configure_rebuild_confirm_submit($form, &$form_state) {
+  node_access_rebuild(TRUE);
   $form_state['redirect'] = 'admin/content/node-settings';
   return;
 }
@@ -2973,7 +2984,7 @@
  */
 function node_access_acquire_grants($node) {
   $grants = module_invoke_all('node_access_records', $node);
-  if (!$grants) {
+  if (empty($grants)) {
     $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
   }
   else {
@@ -3035,28 +3046,125 @@
 }
 
 /**
+ * Flag / unflag the node access grants for rebuilding, or read the current
+ * value of the flag.
+ *
+ * @param $rebuild
+ *   The boolean value to write. Setting to TRUE will cause a message to be
+ *   displayed on admin pages to users with the right permissions, pointing
+ *   them to the 'rebuild' confirm form.
+  * @return
+ *   (If no value was provided for $rebuild) The current value of the flag.
+ */
+function node_access_needs_rebuild($rebuild = NULL) {
+  if (!isset($rebuild)) {
+    return variable_get('node_access_needs_rebuild', FALSE);
+  }
+  elseif ($rebuild) {
+    variable_set('node_access_needs_rebuild', TRUE);
+  }
+  else {
+    variable_del('node_access_needs_rebuild');
+  }
+}
+
+/**
  * Rebuild the node access database. This is occasionally needed by modules
  * that make system-wide changes to access levels.
+ *
+ * Note : As of Drupal 6, node access modules are not required to (and actually
+ * should not) call this in hook_enable/disable anymore.
+ *
+ * @param $batch_mode
+ *   Set to TRUE process in 'batch' mode, spawning processing over several
+ *   HTTP requests (and thus avoiding the risk of PHP timeout if the site
+ *   has a large number of nodes)
+ *
+ *   hook_update_N and any form submit handler are safe places to use this.
+ *   Other cases might consider using the non-batch mode.
  */
-function node_access_rebuild() {
+function node_access_rebuild($batch_mode = FALSE) {
   db_query("DELETE FROM {node_access}");
-  // only recalculate if site is using a node_access module
+  // Only recalculate if the site is using a node_access module.
   if (count(module_implements('node_grants'))) {
-    // If not in 'safe mode', increase the maximum execution time:
-    if (!ini_get('safe_mode')) {
-      set_time_limit(240);
+    if ($batch_mode) {
+      $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);
     }
-    $result = db_query("SELECT nid FROM {node}");
-    while ($node = db_fetch_object($result)) {
-      node_access_acquire_grants(node_load($node->nid, NULL, TRUE));
+    else {
+      // 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));
+      }
     }
   }
   else {
-    // not using any node_access modules. add the default grant.
+    // Not using any node_access modules. Add the default grant.
     db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
   }
+
+  if (!isset($batch)) {
+    drupal_set_message(t('The node access table has been rebuilt.'));
+    node_access_needs_rebuild(FALSE);
+    cache_clear_all();
+  }
+}
+
+/**
+ * Batch operation for node_access_rebuild_batch.
+ *
+ * This is a mutlistep operation : we go through all nodes by packs of 20.
+ * The batch engine interrupts processing and sends progress feedback
+ * after 1 second execution time.
+ */
+function node_access_rebuild_batch_operation(&$context) {
+  if (empty($context['sandbox'])) {
+    // Initiate multistep processing.
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['current_node'] = 0;
+    $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
+  }
+
+  // Process the next 20 nodes.
+  $limit = 20;
+  $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;
+  }
+
+  // Multistep processing : report progress.
+  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.'));
+    node_access_needs_rebuild(FALSE);
+  }
+  else {
+    drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
+  }
   cache_clear_all();
 }
+
 /**
  * @} End of "defgroup node_access".
  */
