Framework to Extend hook_block in Drupal

As you have probably already seen at Drupal.org, Advomatic recently launched the new site for the New York State Senate. This front page announcement has received some interest from other developers, and as promised, I’m detailing the custom block framework that we use there and on other sites.

First the background information. I assume that you already know something about blocks and regions. If not, you might want to review Drupal.org’s documentation on block administration.

As a developer, one area of frustration I’ve frequently encountered in the past is how to control block placement and visibility from code. One of the strengths of Drupal is the abililty to administer most of the site from the site itself. This powerful ability, among others, has spelled doom for the webmaster. At the same time, there are some parts of building a site that a case can be made that would be better accomplished with code. Building blocks is arguably one such case.

Although it’s certainly nice to be able to use the GUI to specify where a block appears, it’s sometimes unwieldly for a developer to go through a dozen screens to enter the PHP block visibility. And even more difficult to go through and make changes later. And the worse piece of all is how to save those changes to a subversion or git repository (as many development shops must do).

Block configuration page

More background: The above screenshot shows a block configuration page. In this textarea, you might have a list of pages for a block to appear on (or not appear on), such as /node, , or /admin*. It’s often useful to have a block displayed on pages that are not so easily defined, such as only on node pages of a certain content type, on user pages (but only if they have more than three blog posts), or only on editorial view pages on the first wednesday of each month.

With core Drupal, site administrators must specify such challenging block visibility settings using this text area with its third setting, “Show if the following PHP code returns TRUE (PHP-mode, experts only)”, and then only if they are able to enter PHP code in the first place. EDIT: And as JohnAlbin points out in a comment to this post, you don’t really want to give administrators the ability to enter PHP code, or to edit a block that is currently using that and inadvertently change it.

In the course of developing sites, we at Advomatic have developed an internal system that addresses these concerns. This framework takes advantage of Drupal’s core offerings, and extends it so that it’s more usable for developers. Prior to this edit, it used hook_block to define our set of custom defined blocks. It then automatically filled each block visibility setting with so that the visibility is deferred back to the module. This allows us both to define and change visibility without needing to scour through dozens of administrative pages, to store these settings in a repository, and if we’re using a development staging and production server deployment scheme, to synchronize those changes through our codebase.

Without further ado, here’s my_module.module. (Note that you would also need to create a corresponding my_module.info file and place them both in /sites/all/modules/my_module. See documentation for more information on creating modules.)

$info_title.
* 2) Define when the block will appear, in my_module_block_display.
* 3) Optionally create a theme file at
* themes/my_module_block_view_content_[$delta].inc
* and/or a template file at
* themes/my_module_block_view_content_[$delta].tpl.php.
* 4) Create a functions here or in that file:
* theme_my_module_block_view_content_[$delta].
* 5) Rebuild your theme to catch the new functions.
* 6) Visit /admin/build/block and place the new blocks in their required
* regions.
*/
?>

The // $Id$ snippet isn’t really needed here; it’s just to encourage proper module development. If you were to contribute a module back to the Drupal community, this would be expanded to display the timestamp and username of the person committing the last change to the file. The rest of this is doxygen documentation telling us how to use the framework.

t(‘Plain block’),
‘fancy_block’ => t(‘Fancy block’),
‘different_block’ => t(‘Different block’),
);
}
switch ($op) {
case ‘list’:
$blocks = array();
foreach ($deltas as $delta => $info) {
$blocks[$delta] = array(
‘info’ => $info,
);
}
return $blocks;
case ‘configure’:
// Here’s an example of how to offer specific block configuration.
// In this case, we would enter the FAPI elements in the include file.
if ($delta == ‘fancy_block’) {
module_load_include(‘inc’, ‘my_module’, “themes/my_module_block_view_content_$delta”);
return my_module_block_configure_form_fancy_block();
}
break;
case ‘save’:
// Again, we would need to save any special configuration options.
if ($delta == ‘fancy_block’) {
module_load_include(‘inc’, ‘my_module’, “themes/my_module_block_view_content_$delta”);
my_module_block_configure_form_save_fancy_block($edit);
}
break;
case ‘view’:
$block = array(
‘subject’ => $deltas[$delta],
‘content’ => ”,
);
module_load_include(‘inc’, ‘my_module’, “themes/my_module_block_view_content_$delta”);
if (my_module_block_display(‘$delta’)) {
// We delegate block content to theme functions defined later.
$block[‘content’] = theme(‘my_module_block_view_content_’. $delta);
}
return $block;
}
}
?>

We can still use the block administration page to determine regions if desired, or we might specify one or two here anyway (and set the status as well). Note that in any case, we must still visit the block administration page, so my experience is it’s easier to just go there and do that.

type == ‘page’;
}
break;
case ‘user:3’:
// Here’s an example of how you might use this framework with an existing
// block. In this case, you would need to enter the following snippet
// into the core “Who’s Online” block visibility:
//
// @TODO: AGAIN, remove the space between the ? and >.
// This hypothetical example would only display this block on the front
// page for users who are logged in.
global $user;
return ($user->uid && drupal_is_front_page());
}
return FALSE;
}
?>

The above is how we determine visibility for blocks.

$block) {
$theme_function = “my_module_block_view_content_$delta”;
// First, include any specially defined files.
$file = module_load_include(‘inc’, ‘my_module’, “themes/$theme_function”);
// Define the block delta content theme function.
$items[$theme_function] = array(
‘arguments’ => array(),
);
// If the module_load_include above was successful, then make sure our
// definition includes that file.
// Note that module_load_include returns NULL if successful, and FALSE
// if not, so we have to test for an explicit FALSE rather than !$file.
if ($file !== FALSE) {
$items[$theme_function][‘file’] = “themes/$theme_function.inc”;
}
if (file_exists(drupal_get_path(‘module’, ‘my_module’) . “/themes/$theme_function.tpl.php”)) {
$items[$theme_function][‘template’] = “themes/$theme_function.tpl.php”;
}
}
return $items;
}
?>

This section uses Drupal’s theme system to dynamically load files with our required theme functions. It could be expanded to use template files (tpl.php) as well if we desired, which could further be overridden by future themes. Conversely, one could easily remove the $file references here and simply include the required theme functions directly in the my_module.module file.

Finally, you would create the theme functions and/or template files, corresponding to those spit out by the above function. Here’s an example, which would go into /sites/all/modules/my_module/themes/my_module_block_view_content_fancy_block.tpl.php.

And the following would go into /sites/all/modules/my_module/themes/my_module_block_view_content_fancy_block.inc, which would define the variables used in the previous template file, as well as allowing the message to be configured in the block configuration page:

title, ‘node/’. $result->nid);
}
}
function my_module_block_configure_form_fancy_block() {
// This returns a form element that’s called when configuring this block.
$form = array();
$form[‘message’] = array(
‘#type’ => ‘textarea’,
‘#title’ => t(‘Fancy block message’),
‘#default_value’ => variable_get(‘my_module_fancy_block_message’, t(‘Here is some interesting data.’)),
);
return $form;
}
function my_module_block_configure_form_save_fancy_block($edit) {
variable_set(‘my_module_fancy_block_message’, $edit[‘message’]);
}
?>

I hope that’s not too arcane for folks. Please let me know any questions or comments you have about this framework!

Aaron Winborn is a developer with Advomatic. Besides development, Advomatic also offers a wide range of other Drupal services, including maintenance and clustered hosting. In addition to helping roll out sites such as the New York State Senate, Air America, and Mozilla, Aaron also contributes heavily to the Drupal community, including such modules as Embedded Media Field and Views SlideShow. He has written Drupal Multimedia, published by Packt Publishing, and is mentoring a Google Summer of Code project to help roll out the upcoming Media module. You can read his blogs at Advomatic and AaronWinborn.com.