Four Kitchens
Insights

Building custom blocks with Drupal 7

6 Min. ReadDevelopment

Introduction

There are times when you need to build a custom block that a site builder can utilize in various places on a page. Drupal 7 provides several hooks that allow you to accomplish this goal:

For this tutorial, we will be using the first four hooks above to build a custom block that will contain WYSIWYG markup and an uploaded picture file. This custom block will be built within a module called custom_block.

Making the Block Selectable

In order to use the block, we have to make it selectable in the block administration menu. To accomplish this, we will use hook_block_info(). Inside this hook, you can assign multiple blocks to your returned array. We will only be adding one block in this tutorial. Add the following code to your module (remember to swap out ‘custom_block’ with the name of your module):

/**
 * Implements hook_block_info().
 */
function custom_block_block_info() {
  $blocks = array();
  $blocks['my_block'] = array(
    'info' => t('My Custom Block'),
  );
  
  return $blocks;
}

After you load this code up in your custom module, you will have the option of selecting and configuring ‘My Custom Block’:

Screenshot of block selection with custom block added.

When you click on configure at this point, you will get the standard block configuration page. We will need additional code in order to enable more block options.

Building the block configuration form

We want to add a WYSIWYG text form for html markup and a file upload widget for our picture file. This can be accomplished within hook_block_configure(). Additionally, we will need two separate form types to pull this off. Inspecting the Form API Reference for Drupal 7, we see that there are two form types that will work perfectly: #text_format and #managed_file.

For the #text_format (WYSIWYG) type form, we need to supply #type, #title, and #default_value. In order for Drupal to remember what you store in this form, you will need to assign a stored variable to #default_value using the variable_get() function.

For the #managed_file (AJAX file upload widget) type form, we need to supply #name, #type, #title, #description, #default_value, #upload_location, and #upload_validators. Again, in order for Drupal to remember the file that we uploaded, #default_value will need to be assigned to the stored file FID variable using the variable_get() function. Additionally, you can specify the subdirectory within your Drupal files directory using #upload_location and allowable file extensions using #upload_validators.

Add the following code to your module:

/**
 * Implements hook_block_configure().
 */
function custom_block_block_configure($delta='') {
  $form = array();
  
  switch($delta) {
    case 'my_block' :
      // Text field form element
      $form['text_body'] = array(
        '#type' => 'text_format',
        '#title' => t('Enter your text here in WYSIWYG format'),
        '#default_value' => variable_get('text_variable', ''),
      );

      // File selection form element
      $form['file'] = array(
        '#name' => 'block_image',
        '#type' => 'managed_file',
        '#title' => t('Choose an Image File'),
        '#description' => t('Select an Image for the custom block.  Only *.gif, *.png, *.jpg, and *.jpeg images allowed.'),
        '#default_value' => variable_get('block_image_fid', ''),
        '#upload_location' => 'public://block_image/',
        '#upload_validators' => array(
          'file_validate_extensions' => array('gif png jpg jpeg'),
        ),
      );
      break;
  }
  return $form;
}

Once you’ve added the code to your module, you should have a block configure form that looks like this:

Screenshot of custom block configuration page.

Handling the file saving

When saving the block, we want to make sure that Drupal will take the necessary steps in order to properly save our text and uploaded file. To accomplish this, we’ll make use of hook_block_save(). This hook is called whenever the ‘Save block’ button is clicked on the block modification form. Two arguments, $delta and $edit, are supplied into hook_block_save(). The $delta variable will contain a string identifier of which block is being configured and $edit will contain an array of submitted block form information.

In order to save our WYSIWYG text, we will use variable_set() function. You’ll need to supply a name and the value as arguments. In our case, the text is captured in the $edit array, specifically within $edit['text_body']['value'].

Saving our uploaded file requires several more steps. First, we need to build a file object for the uploaded file. This is done using the file_load() function and supplying the FID value as the argument. Once the file object is established, the status value needs to be set to FILE_STATUS_PERMANENT. This constant tells Drupal that this file is permanent and should not be deleted. If the file status value is not changed, Drupal assumes that it is temporary and will delete it after the DRUPAL_MAXIMUM_TEMP_FILE_AGE time value is exceeded.

Next, we need to save our file object to the database using the file_save() function. Additionally, we will want to map our block’s usage to the file. To perform this, we first need to build a block object using block_load() while supplying the module name and block name arguments. Then we can set the file usage via the file_usage_add() function. This function passes in the file object, module name, object type associated with the file, and a numeric ID of the object containing the file (in our case, the block BID). Finally, the file FID gets associated with a variable using the variable_set() function, similar to how the text was stored earlier.

Add the following code to your module:

/**
 * Implements hook_block_save().
 */
function custom_block_block_save($delta = '', $edit = array()) {
  switch($delta) {
    case 'my_block' :
      // Saving the WYSIWYG text      
      variable_set('text_variable', $edit['text_body']['value']);

      // Saving the file, setting it to a permanent state, setting a FID variable
      $file = file_load($edit['file']);
      $file->status = FILE_STATUS_PERMANENT;
      file_save($file);
      $block = block_load('custom_block', $delta);
      file_usage_add($file, 'custom_block', 'block', $block->bid);
      variable_set('block_image_fid', $file->fid);
      break;
  }
}

After implementing this code, uploading a file, and saving the block, you should have an entry in your file_usage table like this:

Screenshot of file_usage table entry from uploaded file.

Also, there should be an entry in your file_managed table like this:

Screenshot of the file_managed table entry from uploaded file.

Building the block output

Now all that is left to do is output the block contents in a meaningful way. To achieve this, we will need to use hook_block_view(). Within this hook all that needs to be done is return a renderable array. To decouple the renderable array construction, we’ll place this logic in a separate function called my_block_view() that simply returns the final renderable array.

Within the my_block_view() function, we retrieve the image file using file_load() and supply it with the stored file FID reference variable. We then check to see if the image file path exists and assign it if present. The image markup is built using theme_image() with an array of attributes getting passed in as the argument. The WYSIWYG text markup variable is retrieved using variable_get(). Finally, the block renderable array is constructed using two sub-arrays ‘image’ and ‘message’.

Add the following code to your module:

/**
 * Implements hook_block_view().
 */
function custom_block_block_view($delta='') {
  $block = array();
  
  switch($delta) {
    case 'my_block' :
      $block['content'] = my_block_view();
      break;
  }
  
  return $block;
}

/**
 * Custom function to assemble renderable array for block content.
 * Returns a renderable array with the block content.
 * @return
 *   returns a renderable array of block content.
 */
function my_block_view() {
  $block = array();

  // Capture the image file path and form into HTML with attributes
  $image_file = file_load(variable_get('block_image_fid', ''));
  $image_path = '';

  if (isset($image_file->uri)) {
    $image_path = $image_file->uri;
  }

  $image = theme_image(array(
    'path' => ($image_path),
    'alt' => t('Image description here.'),
    'title' => t('This is our block image.'),
    'attributes' => array('class' => 'class_name'),
  ));

  // Capture WYSIWYG text from the variable
  $text = variable_get('text_variable', '');

  // Block output in HTML with div wrapper
  $block = array(
    'image' => array(
      '#prefix' => '',
      '#type' => 'markup',
      '#markup' => $image,
    ),
    'message' => array(
      '#type' => 'markup',
      '#markup' => $text,
      '#suffix' => '',
    ),
  );
  return $block;
}

Now you have all the necessary pieces in place to output the block on your page. Here is the un-styled block output within the ‘Featured’ section:

Screenshot of final custom block output in the featured section of the Drupal site.

Summary

As you have seen, Drupal 7 provides you with several tools to build custom blocks. Using hooks, the Form API, and various storage/retrieval functions, the block construction possibilites are endless. Have suggestions, comments, or better solutions? Let us know!