Index: modules/block/block.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.admin.inc,v
retrieving revision 1.76
diff -u -p -r1.76 block.admin.inc
--- modules/block/block.admin.inc	28 Mar 2010 11:16:29 -0000	1.76
+++ modules/block/block.admin.inc	20 Apr 2010 22:32:47 -0000
@@ -17,27 +17,28 @@ function block_admin_demo($theme = NULL)
 /**
  * Menu callback for admin/structure/block.
  *
- * @param $theme
+ * @param $theme
  *   The theme to display the administration page for. If not provided, defaults
  *   to the currently used theme.
  */
 function block_admin_display($theme = NULL) {
   global $theme_key;
 
-  drupal_theme_initialize();
-
+  // If no $theme is passed, default to the current theme.
   if (!isset($theme)) {
-    // If theme is not specifically set, rehash for the current theme.
+    if (!isset($theme_key)) {
+      drupal_theme_initialize();
+    }
     $theme = $theme_key;
   }
 
   // Fetch and sort blocks.
-  $blocks = _block_rehash($theme);
+  $blocks = _block_rehash();
   $compare_theme = &drupal_static('_block_compare:theme');
   $compare_theme = $theme;
-  usort($blocks, '_block_compare');
+  usort($blocks[$theme], '_block_compare');
 
-  return drupal_get_form('block_admin_display_form', $blocks, $theme);
+  return drupal_get_form('block_admin_display_form', $blocks[$theme], $theme);
 }
 
 /**
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.416
diff -u -p -r1.416 block.module
--- modules/block/block.module	10 Apr 2010 11:11:46 -0000	1.416
+++ modules/block/block.module	20 Apr 2010 22:32:47 -0000
@@ -304,93 +304,113 @@ function _block_get_renderable_array($li
 }
 
 /**
- * Update the 'block' DB table with the blocks currently exported by modules.
- *
- * @param $theme
- *   The theme to rehash blocks for. If not provided, defaults to the currently
- *   used theme.
+ * Rebuild the {block} table to reflect module and theme changes.
  *
  * @return
- *   Blocks currently exported by modules.
+ *   Array of blocks exported by modules, keyed by theme name.
  */
-function _block_rehash($theme = NULL) {
-  global $theme_key;
-
-  drupal_theme_initialize();
-
-  if (!isset($theme)) {
-    // If theme is not specifically set, rehash for the current theme.
-    $theme = $theme_key;
+function _block_rehash() {
+  // Build array of themes whose blocks need to be rebuilt.
+  $themes = array();
+  foreach(list_themes() as $theme) {
+    if ($theme->status || $theme->name == variable_get('admin_theme', '0')) {
+      $themes[] = $theme->name;
+    }
   }
 
+  // Fetch the existing {block} table, keying it by a hash to make it easier
+  // to iterate when we clean up old blocks.
+  $result = db_query('SELECT * FROM {block}', array(), array(
+    'fetch' => PDO::FETCH_ASSOC,
+  ));
   $old_blocks = array();
-  $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $theme));
   foreach ($result as $old_block) {
-    $old_block = is_object($old_block) ? get_object_vars($old_block) : $old_block;
-    $old_blocks[$old_block['module']][$old_block['delta']] = $old_block;
+    $key = $old_block['theme'] . ':' . $old_block['module'] . ':' . $old_block['delta'];
+    $old_blocks[$key] = $old_block;
   }
 
   $blocks = array();
-  // Valid region names for the theme.
-  $regions = system_region_list($theme);
-
   foreach (module_implements('block_info') as $module) {
-    $module_blocks = module_invoke($module, 'block_info');
-    if ($module_blocks) {
-      foreach ($module_blocks as $delta => $block) {
-        if (empty($old_blocks[$module][$delta])) {
-          // If it's a new block, add identifiers.
-          $block['module'] = $module;
-          $block['delta']  = $delta;
-          $block['theme']  = $theme;
-          if (!isset($block['pages'])) {
-            // {block}.pages is type 'text', so it cannot have a
-            // default value, and not null, so we need to provide
-            // value if the module did not.
-            $block['pages']  = '';
-          }
-          // Add defaults and save it into the database.
-          drupal_write_record('block', $block);
-          // Set region to none if not enabled.
-          $block['region'] = $block['status'] ? $block['region'] : BLOCK_REGION_NONE;
-          // Add to the list of blocks we return.
-          $blocks[] = $block;
-        }
-        else {
-          // If it's an existing block, database settings should overwrite
-          // the code. But aside from 'info' everything that's definable in
-          // code is stored in the database and we do not store 'info', so we
-          // do not need to update the database here.
-          // Add 'info' to this block.
-          $old_blocks[$module][$delta]['info'] = $block['info'];
-          // If the region name does not exist, disable the block and assign it to none.
-          if (!empty($old_blocks[$module][$delta]['region']) && !isset($regions[$old_blocks[$module][$delta]['region']])) {
-            drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $old_blocks[$module][$delta]['info'], '%region' => $old_blocks[$module][$delta]['region'])), 'warning');
-            $old_blocks[$module][$delta]['status'] = 0;
-            $old_blocks[$module][$delta]['region'] = BLOCK_REGION_NONE;
+    if ($module_blocks = module_invoke($module, 'block_info')) {
+      foreach ($themes as $theme) {
+        // Get the regions this theme supports.
+        $regions = system_region_list($theme);
+        $default_region = system_default_region($theme);
+        foreach ($module_blocks as $delta => $block) {
+          $key = $theme . ':' . $module . ':' . $delta;
+          if (isset($old_blocks[$key])) {
+            // Update the entry for this block.
+            // Use block cache mode from hook_block_info(). Default: per-role.
+            $cache_mode = isset($block['cache']) ? $block['cache'] : DRUPAL_CACHE_PER_ROLE;
+            // Order of precedence: 'cache' from hook_block_info(), then block
+            // data from the db, then other keys from hook_block_info().
+            $block = array('cache' => $cache_mode) + $old_blocks[$key] + $block;
+            // Unset the block in $old_blocks. Remaining blocks in that array
+            // will be cleaned up later.
+            unset($old_blocks[$key]);
+            // Tell drupal_write_record() to update the existing row.
+            $update = array('bid');
           }
           else {
-            $old_blocks[$module][$delta]['region'] = $old_blocks[$module][$delta]['status'] ? $old_blocks[$module][$delta]['region'] : BLOCK_REGION_NONE;
+            // Insert an entry for this block.
+            if (isset($block['region']) && $block['region'] == BLOCK_REGION_NONE) {
+              // An empty region is '' in the database, and BLOCK_REGION_NONE
+              // in the code. TODO: standardize this.
+              $block['region'] = '';
+            }
+            $defaults = array(
+              'module' => $module,
+              'delta' => $delta,
+              'theme' => $theme,
+              'status' => 0,
+              'weight' => 0,
+              'region' => '',
+              'custom' => 0,
+              'visibility' => 0,
+              'pages' => '',
+              'title' => '',
+              'cache' => DRUPAL_CACHE_PER_ROLE,
+            );
+            $block += $defaults;
+            if ($block['status'] && empty($block['region'])) {
+              $block['region'] = $default_region;
+            }
+            // Tell drupal_write_record() to insert a new record.
+            $update = array();
           }
-          // Add this block to the list of blocks we return.
-          $blocks[] = $old_blocks[$module][$delta];
-          // Remove this block from the list of blocks to be deleted.
-          unset($old_blocks[$module][$delta]);
+
+          // If the block is enabled and the assigned region is not supported
+          // by this theme, disable the block and unassign the region.
+          if ($block['status'] && !isset($regions[$block['region']])) {
+            // Display a warning if the block was previously active.
+            if (isset($old_blocks[$key])) {
+              drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block['info'], '%region' => $block['region'])), 'warning');
+            }
+            $block['region'] = '';
+            $block['status'] = 0;
+          }
+
+          // Save the block to the {block} table.
+          drupal_write_record('block', $block, $update);
+
+          if ($block['region'] == '') {
+            $block['region'] = BLOCK_REGION_NONE;
+          }
+
+          // Add to the list of blocks we return. 
+          $blocks[$theme][] = $block;
         }
       }
     }
   }
 
-  // Remove blocks that are no longer defined by the code from the database.
-  foreach ($old_blocks as $module => $old_module_blocks) {
-    foreach ($old_module_blocks as $delta => $block) {
-      db_delete('block')
-        ->condition('module', $module)
-        ->condition('delta', $delta)
-        ->condition('theme', $theme)
-        ->execute();
-    }
+  // Clean up the database, removing blocks that are no longer defined.
+  foreach ($old_blocks as $old_block) {
+    db_delete('block')
+      ->condition('bid', $old_block['bid'])
+      ->execute();
   }
+
   return $blocks;
 }
 
Index: modules/block/block.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.test,v
retrieving revision 1.50
diff -u -p -r1.50 block.test
--- modules/block/block.test	10 Apr 2010 11:11:46 -0000	1.50
+++ modules/block/block.test	20 Apr 2010 22:32:47 -0000
@@ -72,9 +72,9 @@ class BlockTestCase extends DrupalWebTes
 
     // Confirm that the custom block has been created, and then query the created bid.
     $this->assertText(t('The block has been created.'), t('Custom block successfully created.'));
-    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+    $bid = db_query('SELECT bid FROM {block_custom} WHERE info = :info', array(':info' => $custom_block['info']))->fetchField();
 
-    // Check to see if the custom block was created by checking that it's in the database..
+    // Check to see if the custom block was created by checking that it's in the database.
     $this->assertNotNull($bid, t('Custom block found in database'));
 
     // Check if the block can be moved to all availble regions.
@@ -204,7 +204,7 @@ class BlockTestCase extends DrupalWebTes
     // Check to see if the block was created by checking that it's in the database.
     $this->assertNotNull($bid, t('Block found in database'));
 
-    // Check if the block can be moved to all availble regions.
+    // Check if the block can be moved to all available regions.
     foreach ($this->regions as $region) {
       $this->moveBlockToRegion($block, $region);
     }
@@ -257,6 +257,27 @@ class BlockTestCase extends DrupalWebTes
     ));
     $this->assertFieldByXPath($xpath, FALSE, t('Custom block found in %region_name region.', array('%region_name' => $region['name'])));
   }
+
+  /**
+   * Test _block_rehash().
+   */
+  function testBlockRehash() {
+    module_enable(array('block_test'));
+    $this->assertTrue(module_exists('block_test'), t('Test block module enabled.'));
+    _block_rehash();
+
+    // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE.
+    $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField();
+    $this->assertEqual($current_caching, DRUPAL_CACHE_PER_ROLE, t('Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.'));    
+
+    // Disable block caching.
+    variable_set('block_test_caching', DRUPAL_NO_CACHE);
+    _block_rehash();
+
+    // Verify that changing the caching mode affected the database.
+    $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField();
+    $this->assertEqual($current_caching, DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE."));
+  }
 }
 
 class NonDefaultBlockAdmin extends DrupalWebTestCase {
Index: modules/block/tests/block_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/tests/block_test.module,v
retrieving revision 1.1
diff -u -p -r1.1 block_test.module
--- modules/block/tests/block_test.module	28 Feb 2010 17:28:38 -0000	1.1
+++ modules/block/tests/block_test.module	20 Apr 2010 22:32:47 -0000
@@ -12,6 +12,7 @@
 function block_test_block_info() {
   $blocks['test_cache'] = array(
     'info' => t('Test block caching'),
+    'cache' => variable_get('block_test_caching', DRUPAL_CACHE_PER_ROLE),
   );
   return $blocks;
 }
