Render any block in Drupal 5

Blocks aren't what you might call "first-class citizens" in Drupal 5 or 6 (or 7, as far as I'm aware). Block functionality is provided by a range of functions and database tables, but ultimately there's no thingness, no Ding an sich tying them together as an object the way that a node of content or a user might be.

Annoyingly, there's no function in Drupal 5 to grab a block and render it. You can render a region, a block container which then makes a number of decisions for you about what blocks should appear in it. Blocks can in principle be uniquely identified by (a) the module which provides the block through its implementation of hook_block() and (b) the "delta", the unique (potentially non-numeric) ID for each block in a given module. Yet there are no functions in the core block module to handle this.

One option is to move to Panels, which I'm doing for this site. Yet if your existing site is heavily based around blocks and regions, implementing Panels can be a lot of overhead just to render a single block in a template.

Here's a function which returns a single block, given the module name and block delta. It's an abstraction from block_list(), the function which returns all the blocks in a given region. You can put this in a shell module with an info file (see the previous post on theme preprocess hooks in Drupal 5 for how to quickly set up an otherwise empty module) or if you're not comfortable with that you can always name it accordingly and put it in template.php

/**
 * Get a single block for themeing
 */
function mymodule_get_block($module, $delta) {
  // User roles mean blocks are still invisible if the user
  // isn't permitted to see them
  global $user;
  $rids = array_keys($user->roles);
  $placeholders = implode(',', array_fill(0, count($rids), '%d'));
 
  // This is still cropped from block_list() , but newlines added
  // for legibility and blogposting
  $result = db_query(
      "SELECT DISTINCT b.* "
    . " FROM {blocks} b LEFT JOIN {blocks_roles} r "
    . "   ON b.module = r.module AND b.delta = r.delta "
    . " WHERE (r.rid IN ($placeholders) OR r.rid IS NULL) "
    . "   AND b.module = '%s' "
    . "   AND b.delta = '%s' "
    . " ORDER BY b.region, b.weight, b.module", 
    array_merge($rids, array($module,$delta)));
 
  // Assemble block info from the module
  while ($block = db_fetch_object($result)) {
    // Invoke the block hook in the supporting 
    // module and convert the return array
    $array = module_invoke($block->module, 'block', 'view', $block->delta);
    if (isset($array) && is_array($array)) {
      foreach ($array as $k => $v) {
        $block->$k = $v;
      }
    }
 
    // Swap in any user-defined title in admin interface
    if ($block->title) {
      $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
    }
 
    return $block;
  }
}

Once this is safely out of the way you can grab and render a block. The following code grabs the fourth block that you ever created through the block admin interface (the "block" module is the "maintainer" of those blocks, and the blocks are numbered starting at zero, hence "3"):

<?php print theme('block', mymodule_get_block('block', 3)); ?>

This is a bit unsafe, though. What if you disable mymodule? The template will break, and depending on your server configuration your visitors might see a pretty ugly error. So use the theme preprocess hooks trick. If you want the block to show in node.tpl.php then you're going to have to bite the bullet and put the bigger code snippet above in a module. Then, in the same module, you can write a hook_preprocess_node as follows:

function mymodule_preprocess_node(&$vars) {
  $vars['myblock'] = theme('block', mymodule_get_block('block', 3));
}

Then you can put just the bare variable in your node.tpl.php, which is much safer:

<?php print $myblock ?>

Turn off mymodule, and this just prints an empty string.

So, ta-da, blocks are now better exposed in your Drupal backend, for you to use in different places in your themeing. Unfortunately that still doesn't make them first-class citizens: they can't be categorized or given an "owner" i.e. an associated user ID; they can't be made to link to nodes in the same robust way that nodes can link to each other with node reference; they certainly can't have any CCK fields hung off them. But, humble though blocks are, they've still got some life left in them yet.