Programmatic setting of block configuration

I've been tasked with fixing some block configuration on a site, following a D6->D7 site upgrade (not my own; a more recent one). The fix needs to run as part of the upgrade - an automatic, post-upgrade task - and so it was best to run it as a Drush command.

Blocks in D7 are still very simple beasts - they're not entities, yet - so you'd think they'd be easy to deal with at the database layer. But there are two complications with block-related database tasks:

  1. A block's numeric identifier is assigned by D7, which means that any given block can end up with a different bid each time the upgrade is run
  2. Some blocks don't even exist in the blocks table until certain caches are cleared, because they're provided by e.g. views which are upgraded separately from Drupal core.

Here's how you get round these two issues. If you need to know how to create a custom Drush command, you should look at this (rather terse) documentation; or, if you already know how to write a simple Drupal module, use drushify.

The tricks are, respectively:

  1. Use a combination of theme, module and delta as a quasi-unique identifier
  2. Use db_merge() to perform a conditional insert/update, so that even if the row doesn't exist yet, it will do.

Positioning blocks in the theme

During the drush sup upgrade, some of the block-in-region configuration was lost. Here's how you can fix that. Firstly, define an array defining the module/delta identifiers for each block, and what region and weight (position) you want it to be in.

$regions_and_deltas = array(
  array('region' => 'content', 'weight' => -26, 'module' => 'system', 'delta' => 'main' ),
  array('region' => 'content', 'weight' => -25, 'module' => 'nodeblock', 'delta' => '39' ),
  array('region' => 'content', 'weight' => -24, 'module' => 'views', 'delta' => 'someview-block_1' ),
);

Here's how you then position those blocks in the theme themename:

foreach($regions_and_deltas as $block) {
  db_merge('block')->key(array(
    'theme' => 'themename',
    'module' => $block['module'],
    'delta' => $block['delta'],
    'status' => 1,
  ))->fields($block)->execute();
}

You should make sure you also set status = 1, as if a block is disabled it could have its status set to zero and won't show whatever you do!

Blanking titles for new blocks

Similarly, here's how I set titles to be blank for new blocks. If you define a region-to-block with Display Suite, then its title should almost invariably be empty, for example; but also, the titles occasionally sprang to life for other Drupal-core blocks:

$blank_titles = array(
  array('module' => 'system',    'delta' => 'navigation'),
  array('module' => 'menu',      'delta' => 'menu-headermenu'),
  array('module' => 'ds_extras', 'delta' => 'homepage_footer'),
);

These were handled with the same foreach() loop as above:

foreach($blank_titles as $block) {
  db_merge('block')->key(array(
    'theme' => 'themename',
    'module' => $block['module'],
    'delta' => $block['delta'],      
  ))->fields(array('title' => '<none>'))->execute();
}

Making blocks visible on what were once Panels pages

Finally, I migrated the homepage from Panels to Display Suite. This allowed us to remove Panels entirely from the build. In doing so, I was able to remove the configuration repetition required when Panels takes over a page including e.g. your sidebar blocks.

However, this did mean that I had to bring the sidebar blocks "back to life" on the homepage. I did this by just blanking their pages configuration:

$unset_pages = array(
  array('module' => 'menu_block', 'delta' => '1' ),
  array('module' => 'block', 'delta' => '1' ),
  array('module' => 'views', 'delta' => 'someview-block_1' ),
  array('module' => 'system', 'delta' => 'navigation' ),
);
 
foreach($unset_pages as $block) {
  db_merge('block')->key(array(
    'theme' => 'themename',
    'module' => $block['module'],
    'delta' => $block['delta'],            
  ))->fields(array('pages' => ''))->execute();
}

In general, once I had db_merge() and a good persistent identifier in mind, each foreach sweep was easier than the first. I can heartily recommend configuring blocks in this way in Drupal 7: it was performant and intuitive, a rare combination!