Index: modules/block/block.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.api.php,v
retrieving revision 1.3
diff -u -p -r1.3 block.api.php
--- modules/block/block.api.php	26 Jan 2009 14:08:42 -0000	1.3
+++ modules/block/block.api.php	5 May 2009 03:20:08 -0000
@@ -172,5 +172,37 @@ function hook_block_view($delta = '') {
 }
 
 /**
+ * Act on blocks prior to rendering.
+ *
+ * This hook allows you to add, remove or modify blocks in the block list.
+ * Alternatively you can set $block->content here, which will override the
+ * content of the block and prevent hook_block_view() from running.
+ *
+ * @param $blocks
+ *   An array of $blocks, keyed by $bid
+ */
+function hook_block_list_alter(&$blocks) {
+  global $language;
+  $result = db_query('SELECT module, delta, language FROM {my_table}')->fetchAll();
+  $block_languages = array();
+  foreach ($result as $record) {
+    $block_languages[$record->module][$record->delta][$record->language] = 1;
+  }
+
+  foreach ($blocks as $key => $block) {
+    if (!isset($block_languages[$block->module][$block->delta])) {
+      // No language setting for this block, leave it in the list.
+      continue;
+    }
+
+    if (!isset($block_languages[$block->module][$block->delta][$language->language])) {
+      // This block should not be displayed for the active language, remove from
+      // the list.
+      unset($blocks[$key]);
+    }
+  }
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.329
diff -u -p -r1.329 block.module
--- modules/block/block.module	26 Apr 2009 16:30:28 -0000	1.329
+++ modules/block/block.module	5 May 2009 03:06:35 -0000
@@ -95,7 +95,7 @@ function block_theme() {
     'block' => array(
       'arguments' => array('block' => NULL),
       'template' => 'block',
-    ),  
+    ),
     'block_admin_display_form' => array(
       'template' => 'block-admin-display-form',
       'file' => 'block.admin.inc',
@@ -572,15 +572,54 @@ function block_list($region) {
  * Load blocks information from the database.
  */
 function _block_load_blocks() {
-  global $user, $theme_key;
+  global $theme_key;
+
+  $queried_blocks = array();
+  $result = db_query('SELECT * FROM {block} WHERE theme = :theme AND status = 1 ORDER BY region, weight, module', array(':theme' => $theme_key));
+  foreach ($result as $record) {
+    $queried_blocks[$record->bid] = $record;
+  }
+
+  // Invoke hook_block_list_alter() to allow modules to alter the list.
+  foreach (module_implements('block_list_alter') as $module) {
+    $function = $module . '_block_list_alter';
+    $function($queried_blocks);
+  }
 
   $blocks = array();
-  $rids = array_keys($user->roles);
-  $result = db_query(db_rewrite_sql("SELECT DISTINCT b.* FROM {block} b LEFT JOIN {block_role} r ON b.module = r.module AND b.delta = r.delta WHERE b.theme = '%s' AND b.status = 1 AND (r.rid IN (" . db_placeholders($rids) . ") OR r.rid IS NULL) ORDER BY b.region, b.weight, b.module", 'b', 'bid'), array_merge(array($theme_key), $rids));
-  while ($block = db_fetch_object($result)) {
-    if (!isset($blocks[$block->region])) {
-      $blocks[$block->region] = array();
+  foreach ($queried_blocks as $key => $block) {
+    $blocks[$block->region][$block->module . '_' . $block->delta] = $block;
+  }
+
+  return $blocks;
+}
+
+/**
+ * Implementation of hook_block_list_alter().
+ */
+function block_block_list_alter(&$blocks) {
+  global $user;
+
+  // Build an array of roles for each block.
+  $blocks_roles = array();
+  $result = db_query('SELECT module, delta, rid FROM {block_role}')->fetchAll();
+  foreach ($result as $record) {
+    $blocks_roles[$record->module][$record->delta][] = $record->rid;
+  }
+
+  foreach ($blocks as $key => $block) {
+    // If a block has no roles associated, it is displayed for every role.
+    // For blocks with roles associated, if none of the user's roles matches
+    // the settings from this block, remove it from the array.
+    if (!isset($block_roles[$block->module][$block->delta])) {
+      // No roles associated.
+    }
+    elseif (!array_intersect($blocks_roles[$block->module][$block->delta], array_keys($user->roles))) {
+      // No match.
+      unset($blocks[$block]);
+      continue;
     }
+
     // Use the user's block visibility setting, if necessary.
     if ($block->custom != 0) {
       if ($user->uid && isset($user->block[$block->module][$block->delta])) {
@@ -593,6 +632,10 @@ function _block_load_blocks() {
     else {
       $enabled = TRUE;
     }
+    if (!$enabled) {
+      unset($blocks[$key]);
+      continue;
+    }
 
     // Match path if necessary.
     if ($block->pages) {
@@ -615,12 +658,11 @@ function _block_load_blocks() {
     else {
       $page_match = TRUE;
     }
-    $block->enabled = $enabled;
-    $block->page_match = $page_match;
-    $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block;
-  }
 
-  return $blocks;
+    if (!$page_match) {
+      unset($blocks[$key]);
+    }
+  }
 }
 
 /**
@@ -636,38 +678,39 @@ function _block_render_blocks($region_bl
   foreach ($region_blocks as $key => $block) {
     // Render the block content if it has not been created already.
     if (!isset($block->content)) {
-      // Erase the block from the static array - we'll put it back if it has content.
+      // Erase the block from the static array - we'll put it back if it has
+      // content.
       unset($region_blocks[$key]);
-      if ($block->enabled && $block->page_match) {
-        // Try fetching the block from cache. Block caching is not compatible with
-        // node_access modules. We also preserve the submission of forms in blocks,
-        // by fetching from cache only if the request method is 'GET' (or 'HEAD').
-        if (!count(module_implements('node_grants')) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
-          $array = $cache->data;
-        }
-        else {
-          $array = module_invoke($block->module, 'block_view', $block->delta);
-          if (isset($cid)) {
-            cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
-          }
+      // Try fetching the block from cache. Block caching is not compatible
+      // with node_access modules. We also preserve the submission of forms in
+      // blocks, by fetching from cache only if the request method is 'GET'
+      // (or 'HEAD').
+      if (!count(module_implements('node_grants')) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
+        $array = $cache->data;
+      }
+      else {
+        $array = module_invoke($block->module, 'block_view', $block->delta);
+        if (isset($cid)) {
+          cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
         }
+      }
 
-        if (isset($array) && is_array($array)) {
-          foreach ($array as $k => $v) {
-            $block->$k = $v;
-          }
+      if (isset($array) && is_array($array)) {
+        foreach ($array as $k => $v) {
+          $block->$k = $v;
         }
-        if (isset($block->content) && $block->content) {
-          // Override default block title if a custom display title is present.
-          if ($block->title) {
-            // Check plain here to allow module generated titles to keep any markup.
-            $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
-          }
-          if (!isset($block->subject)) {
-            $block->subject = '';
-          }
-          $region_blocks["{$block->module}_{$block->delta}"] = $block;
+      }
+      if (isset($block->content) && $block->content) {
+        // Override default block title if a custom display title is present.
+        if ($block->title) {
+          // Check plain here to allow module generated titles to keep any
+          // markup.
+          $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
+        }
+        if (!isset($block->subject)) {
+          $block->subject = '';
         }
+        $region_blocks["{$block->module}_{$block->delta}"] = $block;
       }
     }
   }
Index: modules/block/block.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.test,v
retrieving revision 1.15
diff -u -p -r1.15 block.test
--- modules/block/block.test	26 Apr 2009 16:30:28 -0000	1.15
+++ modules/block/block.test	5 May 2009 01:38:56 -0000
@@ -88,6 +88,37 @@ class BlockTestCase extends DrupalWebTes
   }
 
   /**
+   * Test block visibility.
+   */
+  function testBlockVisibility() {
+    $block = array();
+    $block['title'] = 'Syndicate';
+    $block['module'] = 'node';
+    $block['delta'] = 'syndicate';
+
+    // Set the block to be hidden on any user path, and to be shown only to
+    // authenticated users.
+    $edit = array();
+    $edit['pages'] = 'user*';
+    $edit['roles[2]'] = TRUE;
+    $this->drupalPost('admin/build/block/configure/' . $block['module'] . '/' . $block['delta'], $edit, t('Save block'));
+
+    // Move block to the left sidebar.
+    $this->moveBlockToRegion($block, $this->regions[1]);
+
+    $this->drupalGet('');
+    $this->assertText('Syndicate', t('Block was displayed on the front page.'));
+
+    $this->drupalGet('user*');
+    $this->assertNoText('Syndicate', t('Block was not displayed according to block visibility rules.'));
+
+    // Confirm that the block is not displayed to anonymous users.
+    $this->drupalLogout();
+    $this->drupalGet('');
+    $this->assertNoText('Syndicate', t('Block was not displayed to anonymous users.'));
+  }
+
+  /**
    * Test configuring and moving a module-define block to specific regions.
    */
   function testBlock() {
