Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.407 diff -u -r1.407 block.module --- modules/block/block.module 13 Jan 2010 05:40:03 -0000 1.407 +++ modules/block/block.module 22 Jan 2010 08:11:32 -0000 @@ -294,93 +294,103 @@ } /** - * 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; +function _block_rehash() { + // Prevent race conditions by starting a transaction. + $transaction = db_transaction(); - drupal_theme_initialize(); - - if (!isset($theme)) { - // If theme is not specifically set, rehash for the current theme. - $theme = $theme_key; + // 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 that we can easily + // look up. + $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; + $hash_key = $old_block['theme'] . ':' . $old_block['module'] . ':' . $old_block['delta']; + $old_blocks[$hash_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); + foreach ($module_blocks as $delta => $block) { + $hash_key = $theme . ':' . $module . ':' . $delta; + if (isset($old_blocks[$hash_key])) { + $cache_mode = isset($block['cache']) ? $block['cache'] : DRUPAL_CACHE_PER_ROLE; + // Merge the database's values with the hard-coded values. The + // cache mode should always reflect the module's definition. + $block = array('cache' => $cache_mode) + $old_blocks[$hash_key] + $block; + // 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'] && $block['region'] != BLOCK_REGION_NONE && !isset($regions[$block['region']])) { + 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; + } + // Unset the block in $old_blocks. Remaining blocks in that array + // will be cleaned up later. + unset($old_blocks[$hash_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; + $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; + // 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]); + + // Save the block to the {block} table. + drupal_write_record('block', $block, $update); + + // An empty region is '' in the database, and BLOCK_REGION_NONE in + // the code. + // TODO: standardize this. + 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; } @@ -835,6 +845,8 @@ * Implements hook_flush_caches(). */ function block_flush_caches() { + // Rebuild the {block} table. + _block_rehash(); return array('cache_block'); } Index: modules/block/block.test =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.test,v retrieving revision 1.34 diff -u -r1.34 block.test --- modules/block/block.test 9 Jan 2010 23:03:21 -0000 1.34 +++ modules/block/block.test 22 Jan 2010 08:11:32 -0000 @@ -46,9 +46,9 @@ // 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. @@ -170,7 +170,7 @@ // 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); } @@ -220,6 +220,27 @@ $xpath = '//div[@class="' . $region['class'] . '"]//div[@id="block-' . $block['module'] . '-' . $block['delta'] . '"]/*'; $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.')); + $blocks = _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_block'")->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_block'")->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/block.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.admin.inc,v retrieving revision 1.72 diff -u -r1.72 block.admin.inc --- modules/block/block.admin.inc 15 Jan 2010 10:59:21 -0000 1.72 +++ modules/block/block.admin.inc 22 Jan 2010 08:11:32 -0000 @@ -17,27 +17,28 @@ /** * 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/tests/block_test.module =================================================================== RCS file: modules/block/tests/block_test.module diff -N modules/block/tests/block_test.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/block/tests/block_test.module 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,30 @@ + t('Test block'), + ); + if (variable_get('block_test_caching', FALSE) !== FALSE) { + $blocks['test_block']['cache'] = variable_get('block_test_caching', FALSE); + } + return $blocks; +} + +/** + * Implements hook_block_view(). + */ +function block_test_block_view($delta = 0, $edit = array()) { + return array( + 'subject' => t('Test block'), + 'content' => variable_get('block_test_content', ''), + ); +} Index: modules/block/tests/block_test.info =================================================================== RCS file: modules/block/tests/block_test.info diff -N modules/block/tests/block_test.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/block/tests/block_test.info 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,7 @@ +; $Id$ +name = "Block test" +description = "Provide test blocks." +package = Testing +core = 7.x +files[] = block_test.module +hidden = TRUE